├── docs ├── invoice.pdf ├── ImportButton.jpg ├── Swim Competition Invitation Email Output Sample.png ├── Swim Competition Invitation Leter Output Sample.png └── Notify on Residency Code Changed Email Output Sample.png ├── BETA-create-invoices-in-pdf-from-html ├── data │ ├── images │ │ ├── ImageTest_globe.png │ │ ├── ImageTest_scLogo.png │ │ ├── ImageTest_chancellorSign.png │ │ └── ImageTest_ViseChancellorSign.png │ ├── invoiceDetails_sc.json │ └── style_sc.css └── README.md ├── insights ├── star-trek-example │ ├── target.sql │ ├── custom-function.sql │ ├── intermediate.sql │ ├── star-trek-custom-function.json │ ├── star-trek-custom-table.json │ ├── star-trek-custom-transformation.json │ ├── star-trek-custom-import.json │ ├── README.md │ └── star_trek_original_series_episodes.csv └── hurricane-example │ ├── README.md │ ├── elive-2025-hurricane-report.json │ ├── elive-2025-hurricane-students.json │ └── elive-2025-import-hurdat2.json ├── README.md ├── persons-examples ├── change-person-email.json └── persons-cn-export.json ├── Designer Examples ├── persons-import.json └── persons-export.json ├── swim-competitions-invitation-example ├── swim-team-competitions-table.json ├── swim-team-roster-table.json ├── README.md ├── swim-team-send-player-invitations.json └── swim-team-send-player-invitations-mock.json ├── BETA-residency-code-changed-business-event ├── README.md └── notify-on-residency-code-changed.json ├── BETA-encrypt-student-worker-data ├── README.md ├── encrypt-student-worker-data.json └── encrypt-student-worker-data-mock.json └── BETA-assign-student-priority-registration-group-rulesets ├── README.md └── assign-student-priority-registration-group-rulesets.json /docs/invoice.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellucian-developer/data-connect-examples/HEAD/docs/invoice.pdf -------------------------------------------------------------------------------- /docs/ImportButton.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellucian-developer/data-connect-examples/HEAD/docs/ImportButton.jpg -------------------------------------------------------------------------------- /docs/Swim Competition Invitation Email Output Sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellucian-developer/data-connect-examples/HEAD/docs/Swim Competition Invitation Email Output Sample.png -------------------------------------------------------------------------------- /docs/Swim Competition Invitation Leter Output Sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellucian-developer/data-connect-examples/HEAD/docs/Swim Competition Invitation Leter Output Sample.png -------------------------------------------------------------------------------- /docs/Notify on Residency Code Changed Email Output Sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellucian-developer/data-connect-examples/HEAD/docs/Notify on Residency Code Changed Email Output Sample.png -------------------------------------------------------------------------------- /BETA-create-invoices-in-pdf-from-html/data/images/ImageTest_globe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellucian-developer/data-connect-examples/HEAD/BETA-create-invoices-in-pdf-from-html/data/images/ImageTest_globe.png -------------------------------------------------------------------------------- /BETA-create-invoices-in-pdf-from-html/data/images/ImageTest_scLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellucian-developer/data-connect-examples/HEAD/BETA-create-invoices-in-pdf-from-html/data/images/ImageTest_scLogo.png -------------------------------------------------------------------------------- /BETA-create-invoices-in-pdf-from-html/data/images/ImageTest_chancellorSign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellucian-developer/data-connect-examples/HEAD/BETA-create-invoices-in-pdf-from-html/data/images/ImageTest_chancellorSign.png -------------------------------------------------------------------------------- /BETA-create-invoices-in-pdf-from-html/data/images/ImageTest_ViseChancellorSign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ellucian-developer/data-connect-examples/HEAD/BETA-create-invoices-in-pdf-from-html/data/images/ImageTest_ViseChancellorSign.png -------------------------------------------------------------------------------- /insights/star-trek-example/target.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | custom_insights.random_star_trek_episode() 3 | as episode_title, 4 | 5 | (tr.spriden_last_name || ', ' || tr.spriden_first_name) 6 | as student_name 7 | 8 | FROM 9 | $trekkies tr -------------------------------------------------------------------------------- /insights/star-trek-example/custom-function.sql: -------------------------------------------------------------------------------- 1 | DECLARE 2 | episode_title VARCHAR(100); 3 | BEGIN 4 | SELECT title INTO episode_title 5 | FROM star_trek_episodes 6 | ORDER BY random() 7 | LIMIT 1; 8 | RETURN episode_title; 9 | END; -------------------------------------------------------------------------------- /insights/star-trek-example/intermediate.sql: -------------------------------------------------------------------------------- 1 | select 2 | distinct study.person_uid, 3 | study.program, 4 | study.program_desc, 5 | spriden.spriden_first_name, 6 | spriden.spriden_last_name 7 | from 8 | academic_study study, 9 | spriden spriden 10 | where 11 | spriden.spriden_pidm = person_uid AND 12 | study.program = 'BS_COMPSCI' -------------------------------------------------------------------------------- /insights/star-trek-example/star-trek-custom-function.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "star-trek-custom-function", 3 | "pipeline": [ 4 | "Create Function" 5 | ], 6 | "segments": { 7 | "Create Function": { 8 | "class": "createFunction", 9 | "config": { 10 | "functionName": "random_star_trek_episode", 11 | "replace": true, 12 | "returnType": "VARCHAR(100)", 13 | "bodyStatement": "DECLARE\r\n episode_title VARCHAR(100);\r\nBEGIN\r\n SELECT title INTO episode_title\r\n FROM star_trek_episodes\r\n ORDER BY random()\r\n LIMIT 1;\r\n RETURN episode_title;\r\nEND;", 14 | "drop": false, 15 | "ignoreErrors": false 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /insights/star-trek-example/star-trek-custom-table.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "star-trek-custom-table", 3 | "pipeline": [ 4 | "Create Table" 5 | ], 6 | "segments": { 7 | "Create Table": { 8 | "class": "createTable", 9 | "config": { 10 | "tableName": "student_episodes", 11 | "tableColumns": [ 12 | { 13 | "name": "episode_title", 14 | "type": "character varying(100)", 15 | "nullable": false 16 | }, 17 | { 18 | "name": "student_name", 19 | "type": "character varying(100)", 20 | "nullable": false 21 | } 22 | ], 23 | "drop": false, 24 | "ignoreErrors": false 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data Connect Examples 2 | 3 | This repository contains example Data Connect pipelines. To use these pipelines, you will need to either clone this repository or download a ZIP of it. 4 | 5 | Once you have the examples on your local computer, you can use Data Connect to import them one at a time. 6 | 7 | For example: 8 | 9 | 1. Login to Experience and launch the Data Connect Designer page from the Integration Designer card. 10 | 1. Add a new Package or use an existing package and click on the package name. Note, you will need to create a package name that is not in use by anyone else. 11 | 1. Click on the "+ PIPELINE" button to create an new pipeline. 12 | 1. Enter a name that is unique. This may need to include your institution name to be unique. 13 | 1. Locate and click on the "Import Pipeline" button . 14 | 1. Pick the pipeline json file you wish to import. -------------------------------------------------------------------------------- /insights/star-trek-example/star-trek-custom-transformation.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "star-trek-custom-transformation", 3 | "pipeline": [ 4 | "Intermediate SQL", 5 | "Target SQL" 6 | ], 7 | "segments": { 8 | "Intermediate SQL": { 9 | "class": "intermediateSql", 10 | "config": { 11 | "alias": "trekkies", 12 | "indexColumns": [ 13 | "person_uid" 14 | ], 15 | "debugMode": false, 16 | "sql": "select\n distinct study.person_uid,\n study.program,\n study.program_desc,\n spriden.spriden_first_name,\n spriden.spriden_last_name\nfrom\n academic_study study,\n spriden spriden\nwhere\n spriden.spriden_pidm = person_uid AND\n study.program = 'BS_COMPSCI'", 17 | "ignoreErrors": false 18 | } 19 | }, 20 | "Target SQL": { 21 | "class": "targetSql", 22 | "config": { 23 | "target": "student_episodes", 24 | "sql": "SELECT\n custom_insights.random_star_trek_episode() as episode_title,\n (tr.spriden_last_name || ', ' || tr.spriden_first_name) as student_name\nFROM\n $trekkies tr", 25 | "ignoreErrors": false 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /insights/star-trek-example/star-trek-custom-import.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stark-trek-custom-import", 3 | "parameters": [ 4 | { 5 | "name": "file", 6 | "type": "file", 7 | "required": true 8 | } 9 | ], 10 | "pipeline": [ 11 | "File Reader", 12 | "Line Parser", 13 | "External Data" 14 | ], 15 | "segments": { 16 | "File Reader": { 17 | "class": "S3Accessor", 18 | "config": { 19 | "key": "{{context.file}}", 20 | "errorIfNoFile": false 21 | } 22 | }, 23 | "Line Parser": { 24 | "class": "lineParser" 25 | }, 26 | "External Data": { 27 | "class": "dataLakeWriter", 28 | "config": { 29 | "dataSourceName": "Wikipedia", 30 | "dataSetTemplateName": "Star Trek Data", 31 | "useCSVFile": true, 32 | "appendMode": false, 33 | "table": { 34 | "tableName": "star_trek_episodes", 35 | "tableColumns": [ 36 | { 37 | "name": "season", 38 | "type": "numeric", 39 | "nullable": false 40 | }, 41 | { 42 | "name": "number_in_season", 43 | "type": "numeric", 44 | "nullable": false 45 | }, 46 | { 47 | "name": "number_overall", 48 | "type": "numeric", 49 | "nullable": false 50 | }, 51 | { 52 | "name": "title", 53 | "type": "character varying(100)", 54 | "nullable": false 55 | }, 56 | { 57 | "name": "directed_by", 58 | "type": "character varying(50)", 59 | "nullable": false 60 | }, 61 | { 62 | "name": "original_air_date", 63 | "type": "character varying(50)", 64 | "nullable": false 65 | }, 66 | { 67 | "name": "prod_code", 68 | "type": "character varying(5)", 69 | "nullable": false 70 | } 71 | ] 72 | } 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /BETA-create-invoices-in-pdf-from-html/data/invoiceDetails_sc.json: -------------------------------------------------------------------------------- 1 | { 2 | "topLeftStateColDetails": "XXXXX XXXXXXXX\nSTATE COLLEGE USA PHD SCHOLARSHIP\nSTATE COLLEGE USA\nXXXXX XXXXXX XXXX X, XXXXX X XXXX X\nXXX XXXXXXXXX, XXXXXX\nUSA 15345", 3 | "topRightInvoiceDetails": { 4 | "sponserCode": "XXXXX", 5 | "invoiceDate": "12-Mar-2025", 6 | "invoicePeriod": "2025 SUMMER SEMESTER B", 7 | "invoiceNo": "XXXXXXXXXX", 8 | "dueDate": "21 Mar 2025" 9 | }, 10 | "main": { 11 | "totalInvoice": "$ 130.200.000", 12 | "paymentRequired": "$ 130.200.000", 13 | "address": "Green Office Park 9\nJL. Grand Boulevard, BSD Green Office Park\nBSD City, Reston 15345\nUSA" 14 | }, 15 | "table": { 16 | "12345671 XXXXXX XXX": [ 17 | { 18 | "student": "PhDArts (0020)", 19 | "unit": "SCU455 res in politics", 20 | "period": "SQ1", 21 | "charges": "$ 27.000.000", 22 | "paymentRec": "", 23 | "paymentReq": "$ 27.000.000" 24 | } 25 | ], 26 | "12345672 XXXXXX XXX": [ 27 | { 28 | "student": "PhDArts (0020)", 29 | "unit": "SCU455 res in politics", 30 | "period": "SQ1", 31 | "charges": "$ 27.000.000", 32 | "paymentRec": "", 33 | "paymentReq": "$ 27.000.000" 34 | } 35 | ], 36 | "12345673 XXXXXX XXX": [ 37 | { 38 | "student": "PhDArts (0020)", 39 | "unit": "SCU455 res in politics", 40 | "period": "SQ1", 41 | "charges": "$ 27.000.000", 42 | "paymentRec": "", 43 | "paymentReq": "$ 27.000.000" 44 | } 45 | ], 46 | "12345674 XXXXXX XXX": [ 47 | { 48 | "student": "PhDArts (0020)", 49 | "unit": "SCU455 res in politics", 50 | "period": "SQ1", 51 | "charges": "$ 27.000.000", 52 | "paymentRec": "", 53 | "paymentReq": "$ 27.000.000" 54 | } 55 | ] 56 | }, 57 | "total": { 58 | "tableFootCharges": "$ 130.200.000", 59 | "tableFootPaymentRec": "", 60 | "tableFootPaymentReq": "$ 130.200.000" 61 | }, 62 | 63 | "scaleTemplate": { 64 | "advancedStanding": "0", 65 | "passed": "66", 66 | "currentEnrolment": "0", 67 | "futureEnrolment": "30", 68 | "remaining": "0" 69 | } 70 | } -------------------------------------------------------------------------------- /insights/hurricane-example/README.md: -------------------------------------------------------------------------------- 1 | # Data Connect Pipelines for Insights - Ellucian Live 2025 Hurricane Example 2 | 3 | This repository contains a set of sample pipelines to demonstrate the use of Data Connect with Insights. 4 | 5 | In this demonstration, you will import a list of hurricanes from HURDAT2, and join this data with data from Ellucian Student powered by Banner. This includes a custom function to determine which hurricane landfall points were near Orlando, FL. The list of hurricanes will be joined to a list of students who were enrolled in the same years as the storms. 6 | 7 | The following Data Connect integration pipelines are included: 8 | 9 | * `elive-2025-import-hurdat2` - parses a flat file of hurricane storm information and imports it into Insights 10 | * `elive-2025-hurricane-students` - creates a `hurricane_students` table, a custom function for calculating hurricane proximity, and a custom transform to join the hurricane data with student data 11 | * `elive-2025-hurricane-report` - creates an HTML file by extracting the hurricane student list from Insights 12 | 13 | ## Prerequisites 14 | 15 | You must have the following: 16 | 17 | * Experience Premium with the Integration Designer 18 | * Insights with the Extensibility license to do custom transformations 19 | 20 | ## Importing the Pipelines 21 | 22 | For each pipeline in this repository: 23 | 24 | 1. Create the same pipeline name in the Integration Designer, but with an institution prefix, e.g. myschool-elive-2025-hurricane-report. 25 | 2. Click on Pipeline Settings -> Import to overwrite the pipeline with the pipeline from this repository. 26 | 27 | ## Executing the Pipelines 28 | 29 | For all of these *except the custom import*, you can use the Test button in the Integration Designer to create and/or run the database artifacts. 30 | 31 | For the custom import, you must click the Publish button in the Integration Designer, and then switch to the Integration Packages card in Experience, and run the pipeline as a job. 32 | 33 | You'll need to download the HURDAT2 flat file and upload it when you create and run the job for the import. 34 | https://www.nhc.noaa.gov/data/ 35 | https://www.nhc.noaa.gov/data/hurdat/hurdat2-1851-2024-040425.txt 36 | 37 | The job will take about 15 minutes to run. 38 | 39 | You can use the Insights reporting tool in Experience to view the data that is created by this demonstration. 40 | 41 | -------------------------------------------------------------------------------- /insights/hurricane-example/elive-2025-hurricane-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elive-2025-hurricane-report", 3 | "pipeline": [ 4 | "Extract Data", 5 | "Reducer", 6 | "HTML Formatter", 7 | "File Writer" 8 | ], 9 | "segments": { 10 | "Extract Data": { 11 | "class": "extractData", 12 | "config": { 13 | "selectQuery": "WITH limited_students AS (\r\n SELECT student_name, storm_name, storm_year, storm_proximity,\r\n ROW_NUMBER() OVER (PARTITION BY storm_name) AS rowNum\r\n FROM custom_insights.hurricane_students\r\n order by storm_name\r\n)\r\nSELECT\r\n storm_name, storm_year, storm_proximity, student_name\r\nFROM\r\n limited_students\r\nWHERE\r\n rowNum <= 20\r\nAND \r\n storm_proximity <= 200\r\ngroup by\r\n student_name, storm_name, storm_year, storm_proximity\r\norder by\r\n storm_year desc, storm_name", 14 | "bulkLoadEnabled": true, 15 | "ignoreErrors": false 16 | } 17 | }, 18 | "Reducer": { 19 | "class": "reducer", 20 | "config": { 21 | "accumulator": "payload.records" 22 | } 23 | }, 24 | "HTML Formatter": { 25 | "class": "htmlFormatter", 26 | "config": { 27 | "template": "\n\n\n \n\n\n \n\t\t \n\t\t\t \n\t\t\t \n\t\t\t \n\t\t\t \n\t\t \n\n {{#message.payload.records}}\n\t\t \n\t\t\t \n\t\t\t \n\t\t\t \n\t\t\t \n\t\t \n {{/message.payload.records}}\n\n
HurricaneYearDistance (miles)Student
{{storm_name}}{{storm_year}}{{storm_proximity}}{{student_name}}
\n\n \n\n" 28 | } 29 | }, 30 | "File Writer": { 31 | "class": "s3sink", 32 | "config": { 33 | "key": "storms.html", 34 | "addNewlineAfterEachMessage": false, 35 | "includeTotalCount": false, 36 | "generatePresignedUrl": true, 37 | "writeImmediately": false, 38 | "utfEncoding": "utf-8" 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /persons-examples/change-person-email.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "change-person-email", 3 | "parameters": [ 4 | { 5 | "name": "ethosApiKey", 6 | "type": "string", 7 | "sensitive": true 8 | }, 9 | { 10 | "name": "bannerId", 11 | "type": "string", 12 | "required": true 13 | }, 14 | { 15 | "name": "emailType", 16 | "type": "string", 17 | "default": "business", 18 | "required": true 19 | }, 20 | { 21 | "name": "newEmail", 22 | "type": "string", 23 | "required": true 24 | } 25 | ], 26 | "pipeline": [ 27 | "Get Person", 28 | "compose-put-body", 29 | "Put Person - update email" 30 | ], 31 | "segments": { 32 | "Get Person": { 33 | "class": "ethosProxyGetFilter", 34 | "config": { 35 | "resource": "persons", 36 | "filter": "criteria={\"credentials\":[{\"type\":\"bannerId\",\"value\":\"{{context.bannerId}}\"}]}", 37 | "acceptVersions": [ 38 | "12" 39 | ], 40 | "cache": false, 41 | "queryByPost": false, 42 | "ignoreErrors": false 43 | } 44 | }, 45 | "compose-put-body": { 46 | "class": "JavaScriptTransform", 47 | "config": { 48 | "pushUndefined": true, 49 | "stopOnError": false, 50 | "draft": false, 51 | "code": "function transform (message, context) {\n const { payload: persons } = message;\n const emailType = context.get('emailType');\n const newEmail = context.get('newEmail');\n\n // should be just one\n if (persons.length !== 1) {\n throw new Error(`Expecting one person from bannerId: ${context.get('bannerId')}`);\n }\n\n const person = persons[0];\n\n const putBody = {\n emails: person.emails,\n id: person.id,\n names: person.names\n }\n\n // update the email address\n const email = putBody.emails.find(email => email.type.emailType === emailType);\n\n if (!email) {\n throw new Error(`Email type: ${emailType} not found`);\n }\n\n email.address = newEmail;\n\n return {\n payload: {\n putBody\n }\n };\n}\n" 52 | } 53 | }, 54 | "Put Person - update email": { 55 | "class": "ethosProxyPut", 56 | "config": { 57 | "resource": "persons", 58 | "contentVersion": "12", 59 | "acceptVersion": "12", 60 | "idFromPayload": "$.putBody.id", 61 | "target": "putResult", 62 | "bodyPath": "$.putBody", 63 | "ignoreErrors": false, 64 | "errorPath": "putError" 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Designer Examples/persons-import.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "persons-import", 3 | "parameters": [ 4 | { 5 | "name": "fileName", 6 | "type": "string", 7 | "default": "persons.csv", 8 | "required": true 9 | } 10 | ], 11 | "pipeline": [ 12 | "setup", 13 | "File Reader", 14 | "Line Parser", 15 | "Delimited Parser", 16 | "map-persons", 17 | "Reducer", 18 | "Stringify", 19 | "File Writer" 20 | ], 21 | "segments": { 22 | "setup": { 23 | "class": "JavaScriptTransform", 24 | "config": { 25 | "pushUndefined": true, 26 | "stopOnError": false, 27 | "draft": false, 28 | "code": "function transform (message, context) {\n context.set('importDate', Date.now())\n return message;\n}\n" 29 | } 30 | }, 31 | "File Reader": { 32 | "class": "S3Accessor", 33 | "config": { 34 | "key": "persons.csv", 35 | "errorIfNoFile": false 36 | } 37 | }, 38 | "Line Parser": { 39 | "class": "lineParser" 40 | }, 41 | "Delimited Parser": { 42 | "class": "delimitedParser", 43 | "config": { 44 | "dynamicTyping": false 45 | } 46 | }, 47 | "map-persons": { 48 | "class": "JavaScriptTransform", 49 | "config": { 50 | "pushUndefined": true, 51 | "stopOnError": false, 52 | "draft": false, 53 | "code": "function transform (message, context) {\n const { payload } = message;\n const importDate = new Date(context.get('importDate')).toISOString();\n\n const dateFormatter = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium'});\n const newPayload = {\n firstName: payload['First Name'],\n middleName: payload['Middle Name'],\n lastName: payload['Last Name'],\n fullName: payload['Full Name'],\n dateOfBirth: payload['Date of Birth'],\n dateOfBirthFormatted: payload['Date of Birth'] ? dateFormatter.format(new Date(payload['Date of Birth'])) : '',\n gender: payload['Gender'],\n race: payload['Race'],\n ethnicity: payload['Ethnicity'],\n importDate\n }\n \n return {\n payload: newPayload\n };\n}\n" 54 | } 55 | }, 56 | "Reducer": { 57 | "class": "reducer", 58 | "config": { 59 | "accumulator": "persons" 60 | } 61 | }, 62 | "Stringify": { 63 | "class": "stringify" 64 | }, 65 | "File Writer": { 66 | "class": "s3sink", 67 | "config": { 68 | "key": "personsImport.json", 69 | "addNewlineAfterEachMessage": true, 70 | "includeTotalCount": true, 71 | "generatePresignedUrl": true 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /insights/star-trek-example/README.md: -------------------------------------------------------------------------------- 1 | # Data Connect Pipelines for Insights 2 | 3 | This repository contains a set of sample pipelines to demonstrate the use of Data Connect with Insights. 4 | 5 | In this demonstration, you will import a list of Star Trek episodes from a CSV file, and assign a random star trek episode to each student who is a computer science major. Then you will produce an HTML report with the assignment list. 6 | 7 | The following Data Connect integration pipelines are included: 8 | 9 | * `star-trek-custom-table` - creates the `student_episodes` table to store the final assignments 10 | * `star-trek-custom-import` - imports the CSV data from `star_trek_original_series_epsiodes.csv` into the `star_trek_episodes` table in Insights 11 | * `star-trek-custom-function` - creates a `random_star_trek_episode` custom function in Insights 12 | * `star-trek-custom-transformation` - creates the ETL sequence to populate `student_episodes` with data using the custom function 13 | * `star-trek-custom-extract` - creates an HTML file with the list of assignments extracted from the `student_episodes` table in Insights 14 | 15 | Other files included for reference and easy reading: 16 | 17 | * `custom-function.sql` - the function code 18 | * `intermediate.sql` - the code for the Intermediate SQL step on the custom transformation 19 | * `target.sql` - the code for the Target SQL step on the custom transformation 20 | * `report.html` - the HTML code for the final report 21 | 22 | ## Prerequisites 23 | 24 | You must have the following: 25 | 26 | * Experience Premium with the Integration Designer 27 | * Insights with the Extensibility license to do custom transformations 28 | 29 | ## Importing the Pipelines 30 | 31 | For each pipeline in this repository: 32 | 33 | 1. Create the same pipeline name in the Integration Designer, but with an institution prefix, e.g. myschool-star-trek-custom-table. 34 | 2. Click on Pipeline Settings -> Import to overwrite the pipeline with the pipeline from this repository. 35 | 36 | ## Executing the Pipelines 37 | 38 | For all of these *except the custom import*, you can use the Test button in the Integration Designer to create and/or run the database artifacts. 39 | 40 | For the custom import, you must click the Publish button in the Integration Designer, and then switch to the Integration Packages card in Experience, and run the pipeline as a job. When you create the job, select the `star_trek_original_series_episodes.csv` file for upload. The job will take about 15 minutes to run. 41 | 42 | You can use the Insights reporting tool in Experience to view the data that is created by this demonstration. 43 | 44 | -------------------------------------------------------------------------------- /swim-competitions-invitation-example/swim-team-competitions-table.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Create and Populate Team Competitions Table", 3 | "pipeline": [ 4 | "Create Table", 5 | "Populate Table", 6 | "Test Query" 7 | ], 8 | "segments": { 9 | "Create Table": { 10 | "description": "Creates a team competitions table with specified columns.", 11 | "class": "createTable", 12 | "config": { 13 | "tableName": "team_competitions", 14 | "tableColumns": [ 15 | { 16 | "name": "player_name", 17 | "type": "text", 18 | "nullable": true 19 | }, 20 | { 21 | "name": "competition", 22 | "type": "text", 23 | "nullable": true 24 | } 25 | ], 26 | "drop": false, 27 | "ignoreErrors": false 28 | } 29 | }, 30 | "Populate Table": { 31 | "description": "Populates the team competitions table with sample data records.", 32 | "class": "targetSql", 33 | "config": { 34 | "target": "team_competitions", 35 | "sql": "select 'John Doe' player_name, 'Regional Qualifiers' competition union all\r\nselect 'John Doe', 'State Championship' union all\r\nselect 'John Doe', 'National Invitational' union all\r\nselect 'Emma Smith', 'Regional Qualifiers' union all\r\nselect 'Emma Smith', 'National Invitational' union all\r\nselect 'Emma Smith', 'Winter Classic' union all\r\nselect 'Liam Johnson', 'State Championship' union all\r\nselect 'Liam Johnson', 'Winter Classic' union all\r\nselect 'Liam Johnson', 'Summer Games' union all\r\nselect 'Olivia Brown', 'Regional Qualifiers' union all\r\nselect 'Olivia Brown', 'Summer Games' union all\r\nselect 'Ava Wilson', 'State Championship' union all\r\nselect 'Ava Wilson', 'Spring Tournament' union all\r\nselect 'Noah Davis', 'Winter Classic' union all\r\nselect 'Noah Davis', 'National Invitational' union all\r\nselect 'Noah Davis', 'Spring Tournament' union all\r\nselect 'Noah Davis', 'Regional Qualifiers' union all\r\nselect 'Ethan Martinez', 'Summer Games' union all\r\nselect 'Ethan Martinez', 'Regional Qualifiers' union all\r\nselect 'Sophia Taylor', 'Spring Tournament' union all\r\nselect 'Sophia Taylor', 'State Championship' union all\r\nselect 'Sophia Taylor', 'National Invitational' union all\r\nselect 'Mason Anderson', 'National Invitational' union all\r\nselect 'Mason Anderson', 'Winter Classic' union all\r\nselect 'Isabella Thomas', 'Summer Games' union all\r\nselect 'Isabella Thomas', 'Spring Tournament' union all\r\nselect 'Isabella Thomas', 'Regional Qualifiers';\r\n", 36 | "append": false, 37 | "ignoreErrors": false 38 | } 39 | }, 40 | "Test Query": { 41 | "description": "Executes a query to verify data insertion in the team competitions table.", 42 | "class": "extractData", 43 | "config": { 44 | "selectQuery": "select\r\n player_name,\r\n competition\r\nfrom \r\n custom_insights.team_competitions ", 45 | "bulkLoadEnabled": true, 46 | "ignoreErrors": false 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /insights/hurricane-example/elive-2025-hurricane-students.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elive-2025-hurricane-students", 3 | "pipeline": [ 4 | "Create Table", 5 | "Create Function", 6 | "Target SQL" 7 | ], 8 | "segments": { 9 | "Create Table": { 10 | "class": "createTable", 11 | "config": { 12 | "tableName": "hurricane_students", 13 | "tableColumns": [ 14 | { 15 | "name": "storm_name", 16 | "type": "character varying(50)", 17 | "nullable": false 18 | }, 19 | { 20 | "name": "storm_year", 21 | "type": "character varying(4)", 22 | "nullable": false 23 | }, 24 | { 25 | "name": "storm_proximity", 26 | "type": "numeric", 27 | "nullable": false 28 | }, 29 | { 30 | "name": "student_name", 31 | "type": "character varying(50)", 32 | "nullable": false 33 | } 34 | ], 35 | "drop": false, 36 | "ignoreErrors": false 37 | } 38 | }, 39 | "Create Function": { 40 | "class": "createFunction", 41 | "config": { 42 | "functionName": "closest_landfall_distance_from_orlando", 43 | "replace": true, 44 | "argumentStatement": "storm_name TEXT", 45 | "returnType": "numeric", 46 | "bodyStatement": "DECLARE\r\n closest_distance DOUBLE PRECISION;\r\nBEGIN\r\n SELECT \r\n MIN(\r\n 3958.8 * acos(\r\n cos(radians(28.5)) * cos(radians(\r\n CASE \r\n WHEN lat LIKE '%N' THEN CAST(LEFT(lat, LENGTH(lat) - 1) AS FLOAT)\r\n WHEN lat LIKE '%S' THEN -CAST(LEFT(lat, LENGTH(lat) - 1) AS FLOAT)\r\n END\r\n )) * \r\n cos(radians(\r\n CASE \r\n WHEN lon LIKE '%E' THEN CAST(LEFT(lon, LENGTH(lon) - 1) AS FLOAT)\r\n WHEN lon LIKE '%W' THEN -CAST(LEFT(lon, LENGTH(lon) - 1) AS FLOAT)\r\n END\r\n ) - radians(-81.3)) + \r\n sin(radians(28.5)) * sin(radians(\r\n CASE \r\n WHEN lat LIKE '%N' THEN CAST(LEFT(lat, LENGTH(lat) - 1) AS FLOAT)\r\n WHEN lat LIKE '%S' THEN -CAST(LEFT(lat, LENGTH(lat) - 1) AS FLOAT)\r\n END\r\n ))\r\n )\r\n ) \r\n INTO closest_distance\r\n FROM hurdat2\r\n WHERE name = storm_name;\r\n\r\n RETURN closest_distance;\r\nEND;", 47 | "drop": false, 48 | "ignoreErrors": false 49 | } 50 | }, 51 | "Target SQL": { 52 | "class": "targetSql", 53 | "config": { 54 | "target": "hurricane_students", 55 | "sql": "select\n hd.name as storm_name,\n hd.year as storm_year,\n ceil(custom_insights.closest_landfall_distance_from_orlando(hd.name)) as storm_proximity,\n ao.name as student_name\nfrom\n custom_insights.hurdat2 hd,\n academic_outcome ao\nwhere\n ao.ACADEMIC_YEAR_GRADUATION = hd.year", 56 | "append": false, 57 | "ignoreErrors": false 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /BETA-create-invoices-in-pdf-from-html/README.md: -------------------------------------------------------------------------------- 1 | # Data Connect Pipelines Demonstrating Create PDF Fitting 2 | 3 | This repository provides sample pipelines that showcase how to generate PDFs using Create PDF fitting and deliver them using various destination fittings, including File Writer, SFTP PUT, Send Notification, and Create ZIP. 4 | 5 | --- 6 | ## Pipelines Included 7 | 8 | * `send-email-notification-of-invoice-in-pdf-from-html` - This pipeline is used to read multiple files at the same time from s3 and convert them to PDF(Invoice) and send them over email 9 | * `send-invoice-over-SFTP-server-in-pdf-from-html` - This pipeline is used to read multiple files at the same time from s3 and convert them to PDF(Invoice) and send them over SFTP server 10 | * `zip-file-of-invoice-in-pdf-from-html` - This pipeline is used to read multiple files at the same time from s3 and convert them to PDF(Invoice) and create a zip file of all the invoice 11 | 12 | --- 13 | 14 | ## Prerequisites 15 | 16 | You will need: 17 | 18 | * Experience Premium with Integration Designer 19 | * Banner system access 20 | * A verified email address for sending notifications 21 | * A SFTP Server with valid credentials 22 | * An S3 bucket with valid credentials and all required files uploaded to the `data` folder 23 | 24 | --- 25 | 26 | ## Pipeline Details 27 | 28 | ### `send-email-notification-of-invoice-in-pdf-from-html` 29 | 30 | > **Note:** The `send-invoice-over-SFTP-server-in-pdf-from-html` and `zip-file-of-invoice-in-pdf-from-html` pipelines follow similar steps; the main difference is that the first sends the PDF files over an SFTP server, while the latter creates a ZIP archive of the PDF files. 31 | 32 | This pipeline performs the following steps: 33 | 1. Reads all images, converts them into buffers, and adds them to the `context` 34 | 2. Read the `.css` file and add to the `context` 35 | 3. Read the `invoiceDetails.json` file process it and add to the `context` 36 | 4. Convert PDF from HTML formatter which contains `HTML` and `context` data. 37 | 5. Sends a notification email to the user. 38 | 39 | --- 40 | 41 | ## Execution Instructions 42 | 43 | 1. **Create and Publish the Pipeline:** 44 | Use Integration Designer to create and publish the pipeline. The pipeline is pre-configured with several parameters, some of which have default values. Be sure to specify the email addresses for both the "to" and "from" fields, as well as the S3 bucket name, access key, and secret access key. 45 | 2. **Configure the Job:** 46 | In Integration Packages, a new job should be created for the pipeline. 47 | During job setup, values for the sender and receiver email addresses, S3 bucket name, and credentials must be provided. 48 | The job should be configured to trigger on the specified event, with completion alerts set as required. 49 | 50 | 51 | --- 52 | 53 | ## Pipeline Outputs 54 | 55 | Below are sample outputs generated by the pipeline: 56 | 57 | ![Pipeline Output 1 - Email Sent](../docs/invoice.pdf) 58 | 59 | --- 60 | 61 | ## Other Use Cases 62 | 63 | This pipeline pattern is especially useful in higher education institutes for sending custom documents, such as: 64 | 65 | - Transcripts 66 | - Financial Statements 67 | - Housing Assignments 68 | - Invoice 69 | 70 | 71 | ## Conclusion 72 | 73 | This example demonstrates how higher education institutes, powered by Data Connect, can custom documents over email as an attachment or ZIP file. -------------------------------------------------------------------------------- /BETA-residency-code-changed-business-event/README.md: -------------------------------------------------------------------------------- 1 | # Trigger by Event Pipelines: Consuming and processing Business Events on Data Connect (BETA) 2 | 3 | This repository provides a sample pipeline demonstrating how to use Data Connect to automate processes triggered by business events in Banner. The example focuses on updating a student's residency code and notifying the student when this change occurs. 4 | 5 | **Data Connect plays a central role in enabling seamless, event-driven automation across Banner and related systems.** By leveraging Data Connect, you can quickly build, deploy, and manage integrations that respond to real-time business events—without the need for custom code or manual intervention. 6 | 7 | In this scenario, a business event is triggered after a student's residency code for a specific term is updated in Banner. The pipeline listens for this event, gathers relevant student information, and sends a notification email. 8 | 9 | > **Note:** You must have the business event configured in Banner Event Publisher (BEP) and a subscriber application set up in Ethos Integration, subscribed to the corresponding event. 10 | 11 | --- 12 | 13 | ## Pipelines Included 14 | 15 | * `notify-on-residency-code-changed` – Consumes and processes the business event, then notifies the affected student. 16 | 17 | --- 18 | 19 | ## Prerequisites 20 | 21 | You will need: 22 | 23 | * Experience Premium with Integration Designer 24 | * Banner system access 25 | * A verified email address for sending notifications 26 | 27 | --- 28 | 29 | ## Pipeline Details 30 | 31 | ### `notify-on-residency-code-changed` 32 | 33 | This pipeline performs the following steps: 34 | 35 | 1. Consumes the relevant business event. 36 | 2. Retrieves residency type information for filtering. 37 | 3. Gathers student details (name, email, residency type). 38 | 4. Sends a notification email to the student. 39 | 5. Confirms the business event. 40 | 41 | **Sample Email Template:** 42 | - Student name 43 | - New residency type 44 | - Notification about impact on registration fees 45 | 46 | --- 47 | 48 | ## Execution Instructions 49 | 50 | 1. **Configure the Business Event and Connect Additional Data (in BEP):** 51 | In Banner Event Publisher (BEP), set up a business event to listen for changes to the residency code field (`SGBSTDN_RESD_CODE`) in the `SGBSTDN` table. 52 | Also, ensure the event includes additional data fields for student email (`GOREMAL_EMAIL_ADDRESS`), first name (`SPRIDEN_FIRST_NAME`), and last name (`SPRIDEN_LAST_NAME`). 53 | 54 | 2. **Subscribe to the Event:** 55 | Set up a subscriber application in Ethos Integration and subscribe to the business event from the appropriate Banner instance. 56 | 57 | 3. **Create and Publish the Pipeline:** 58 | Use Integration Designer to create and publish the pipeline. The pipeline is already configured to use two parameters: one for the Ethos API key and one for the sender email address for outgoing notifications. To use the Trigger by Event feature, the pipeline must include the Ethos Consume Event and Ethos Consume Confirm fittings, which enable business event consumption, processing, and confirmation. 59 | 60 | 4. **Configure the Job:** 61 | In Integration Packages, create a new job for the pipeline. 62 | When setting up the job, you will be prompted to provide values for both the Ethos API key of the subscriber application and the sender email address parameters. 63 | Set the job to be triggered by the event and configure completion alerts as needed. 64 | 65 | Once configured, whenever a student's residency code is assigned for the first time for a specific term, the business event will trigger and the pipeline job will execute automatically. 66 | 67 | --- 68 | 69 | ## Pipeline Outputs 70 | 71 | Below are sample outputs generated by the pipeline: 72 | 73 | ![Pipeline Output 1 - Email Sent](../docs/Notify%20on%20Residency%20Code%20Changed%20Email%20Output%20Sample.png) 74 | 75 | --- 76 | 77 | ## Other Use Cases 78 | 79 | This pipeline pattern is especially useful in higher education for automating workflows, such as: 80 | 81 | - Notifying students of changes to academic status 82 | - Alerting staff to critical updates 83 | - Automating compliance or reporting processes 84 | 85 | --- 86 | 87 | ## Conclusion 88 | 89 | This example demonstrates how event-driven pipelines, powered by Data Connect, can streamline communication and automate business processes in Banner environments. Data Connect makes it easy to build robust, scalable automations that respond instantly to changes in your data. -------------------------------------------------------------------------------- /insights/hurricane-example/elive-2025-import-hurdat2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elive-2025-import-hurdat2", 3 | "parameters": [ 4 | { 5 | "name": "file", 6 | "type": "string", 7 | "default": "hurdat2-1851-2023-051124.txt", 8 | "required": true 9 | }, 10 | { 11 | "name": "limit", 12 | "type": "number", 13 | "required": true, 14 | "default": 30 15 | } 16 | ], 17 | "pipeline": [ 18 | "File Reader", 19 | "Reducer", 20 | "JavaScript Transform", 21 | "For Each", 22 | "External Data" 23 | ], 24 | "segments": { 25 | "File Reader": { 26 | "class": "S3Accessor", 27 | "config": { 28 | "key": "{{context.file}}", 29 | "binary": false, 30 | "errorIfNoFile": false 31 | } 32 | }, 33 | "Reducer": { 34 | "class": "reducer", 35 | "config": { 36 | "accumulator": "payload.lines" 37 | } 38 | }, 39 | "JavaScript Transform": { 40 | "class": "JavaScriptTransform", 41 | "config": { 42 | "pushUndefined": false, 43 | "stopOnError": false, 44 | "draft": false, 45 | "code": "function transform (message, context) {\n const data = message.payload.lines.join('').trim().split('\\n');\n const storms = [];\n let lastStorm;\n data.forEach(line => {\n if (line.startsWith('AL')) {\n lastStorm = {\n basin: line.slice(0, 2),\n number: parseInt(line.slice(2, 4)),\n year: line.slice(4, 8),\n name: line.slice(18, 28).trim()\n }\n storms.push(lastStorm)\n } else if (line.includes(' L,')) {\n/*\n29.1 (Spaces 24-27) – Latitude\nN (Space 28, before 5th comma) – Hemisphere – North or South\n90.2 (Spaces 31-35) – Longitude\nW (Space 36, before 6th comma) – Hemisphere – West or East\n130 (Spaces 39-41, before 7th comma) – Maximum sustained wind (in knots)\n931 (Spaces 44-47, before 8th comma) – Minimum Pressure (in millibars)\n*/\n if (lastStorm.landfall) {\n lastStorm = {\n basin: lastStorm.basin,\n number: lastStorm.number,\n name: lastStorm.name,\n year: lastStorm.year\n }\n storms.push(lastStorm);\n }\n lastStorm.landfall = true;\n lastStorm.lat = line.slice(23, 28).trim()\n lastStorm.lon = line.slice(30, 36).trim()\n lastStorm.wind = parseInt(line.slice(38, 42).trim())\n lastStorm.pressure = parseInt(line.slice(43, 48).trim())\n }\n })\n \n const limit = parseInt(context.get('limit'));\n message.payload = { storms: storms.filter(s => s.landfall).slice(-1 * limit) }\n\n return message;\n}\n" 46 | } 47 | }, 48 | "For Each": { 49 | "class": "forEach", 50 | "config": { 51 | "source": "payload.storms" 52 | } 53 | }, 54 | "External Data": { 55 | "class": "dataLakeWriter", 56 | "config": { 57 | "dataSourceName": "noaa", 58 | "dataSetTemplateName": "hurdat2", 59 | "useCSVFile": false, 60 | "appendMode": false, 61 | "detail": { 62 | "columnHeaders": true, 63 | "columns": [ 64 | { 65 | "path": "name" 66 | }, 67 | { 68 | "path": "basin" 69 | }, 70 | { 71 | "path": "year" 72 | }, 73 | { 74 | "path": "number" 75 | }, 76 | { 77 | "path": "lat" 78 | }, 79 | { 80 | "path": "lon" 81 | }, 82 | { 83 | "path": "wind" 84 | }, 85 | { 86 | "path": "pressure" 87 | } 88 | ] 89 | }, 90 | "table": { 91 | "tableName": "hurdat2", 92 | "tableColumns": [ 93 | { 94 | "name": "name", 95 | "type": "character varying(50)", 96 | "nullable": true 97 | }, 98 | { 99 | "name": "basin", 100 | "type": "character varying(2)", 101 | "nullable": false 102 | }, 103 | { 104 | "name": "year", 105 | "type": "character varying(4)", 106 | "nullable": false 107 | }, 108 | { 109 | "name": "number", 110 | "type": "numeric", 111 | "nullable": false 112 | }, 113 | { 114 | "name": "lat", 115 | "type": "character varying(10)", 116 | "nullable": true 117 | }, 118 | { 119 | "name": "lon", 120 | "type": "character varying(10)", 121 | "nullable": true 122 | }, 123 | { 124 | "name": "wind", 125 | "type": "numeric", 126 | "nullable": false 127 | }, 128 | { 129 | "name": "pressure", 130 | "type": "numeric", 131 | "nullable": false 132 | } 133 | ] 134 | } 135 | } 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /insights/star-trek-example/star_trek_original_series_episodes.csv: -------------------------------------------------------------------------------- 1 | season,number_in_season,number_overall,title,directed_by,original_air_date,prod_code 2 | 1,1,1,The Man Trap,Marc Daniels,"September 8, 1966",6 3 | 1,2,2,Charlie X,Lawrence Dobkin,"September 15, 1966",8 4 | 1,3,3,Where No Man Has Gone Before,James Goldstone,"September 22, 1966",02b 5 | 1,4,4,The Naked Time,Marc Daniels,"September 29, 1966",7 6 | 1,5,5,The Enemy Within,Leo Penn,"October 6, 1966",5 7 | 1,6,6,Mudd's Women,Harvey Hart,"October 13, 1966",4 8 | 1,7,7,What Are Little Girls Made Of?,James Goldstone,"October 20, 1966",10 9 | 1,8,8,Miri,Vincent McEveety,"October 27, 1966",12 10 | 1,9,9,Dagger of the Mind,Vincent McEveety,"November 3, 1966",11 11 | 1,10,10,The Corbomite Maneuver,Joseph Sargent,"November 10, 1966",3 12 | 1,11,11,The Menagerie,Marc Daniels,"November 17, 1966",16 13 | 1,12,12,The Menagerie,Robert Butler,"November 24, 1966",16 14 | 1,13,13,The Conscience of the King,Gerd Oswald,"December 8, 1966",13 15 | 1,14,14,Balance of Terror,Vincent McEveety,"December 15, 1966",9 16 | 1,15,15,Shore Leave,Robert Sparr,"December 29, 1966",17 17 | 1,16,16,The Galileo Seven,Robert Gist,"January 5, 1967",14 18 | 1,17,17,The Squire of Gothos,Don McDougall,"January 12, 1967",18 19 | 1,18,18,Arena,Joseph Pevney,"January 19, 1967",19 20 | 1,19,19,Tomorrow Is Yesterday,Michael O'Herlihy,"January 26, 1967",21 21 | 1,20,20,Court Martial,Marc Daniels,"February 2, 1967",15 22 | 1,21,21,The Return of the Archons,Joseph Pevney,"February 9, 1967",22 23 | 1,22,22,Space Seed,Marc Daniels,"February 16, 1967",24 24 | 1,23,23,A Taste of Armageddon,Joseph Pevney,"February 23, 1967",23 25 | 1,24,24,This Side of Paradise,Ralph Senensky,"March 2, 1967",25 26 | 1,25,25,The Devil in the Dark,Joseph Pevney,"March 9, 1967",26 27 | 1,26,26,Errand of Mercy,John Newland,"March 23, 1967",27 28 | 1,27,27,The Alternative Factor,Gerd Oswald,"March 30, 1967",20 29 | 1,28,28,The City on the Edge of Forever,Joseph Pevney,"April 6, 1967",28 30 | 1,29,29,Operation -- Annihilate!,Herschel Daugherty,"April 13, 1967",29 31 | 2,1,30,Amok Time,Joseph Pevney,"September 15, 1967",34 32 | 2,2,31,Who Mourns for Adonais?,Marc Daniels,"September 22, 1967",33 33 | 2,3,32,The Changeling,Marc Daniels,"September 29, 1967",37 34 | 2,4,33,"Mirror, Mirror",Marc Daniels,"October 6, 1967",39 35 | 2,5,34,The Apple,Joseph Pevney,"October 13, 1967",38 36 | 2,6,35,The Doomsday Machine,Marc Daniels,"October 20, 1967",35 37 | 2,7,36,Catspaw,Joseph Pevney,"October 27, 1967",30 38 | 2,8,37,"I, Mudd",Marc Daniels,"November 3, 1967",41 39 | 2,9,38,Metamorphosis,Ralph Senensky,"November 10, 1967",31 40 | 2,10,39,Journey to Babel,Joseph Pevney,"November 17, 1967",44 41 | 2,11,40,Friday's Child,Joseph Pevney,"December 1, 1967",32 42 | 2,12,41,The Deadly Years,Joseph Pevney,"December 8, 1967",40 43 | 2,13,42,Obsession,Ralph Senensky,"December 15, 1967",47 44 | 2,14,43,Wolf in the Fold,Joseph Pevney,"December 22, 1967",36 45 | 2,15,44,The Trouble with Tribbles,Joseph Pevney,"December 29, 1967",42 46 | 2,16,45,The Gamesters of Triskelion,Gene Nelson,"January 5, 1968",46 47 | 2,17,46,A Piece of the Action,James Komack,"January 12, 1968",49 48 | 2,18,47,The Immunity Syndrome,Joseph Pevney,"January 19, 1968",48 49 | 2,19,48,A Private Little War,Marc Daniels,"February 2, 1968",45 50 | 2,20,49,Return to Tomorrow,Ralph Senensky,"February 9, 1968",51 51 | 2,21,50,Patterns of Force,Vincent McEveety,"February 16, 1968",52 52 | 2,22,51,By Any Other Name,Marc Daniels,"February 23, 1968",50 53 | 2,23,52,The Omega Glory,Vincent McEveety,"March 1, 1968",54 54 | 2,24,53,The Ultimate Computer,John Meredyth Lucas,"March 8, 1968",53 55 | 2,25,54,Bread and Circuses,Ralph Senensky,"March 15, 1968",43 56 | 2,26,55,Assignment: Earth,Marc Daniels,"March 29, 1968",55 57 | 3,1,56,Spock's Brain,Marc Daniels,"September 20, 1968",61 58 | 3,2,57,The Enterprise Incident,John Meredyth Lucas,"September 27, 1968",59 59 | 3,3,58,The Paradise Syndrome,Jud Taylor,"October 4, 1968",58 60 | 3,4,59,And the Children Shall Lead,Marvin Chomsky,"October 11, 1968",60 61 | 3,5,60,Is There in Truth No Beauty?,Ralph Senensky,"October 18, 1968",62 62 | 3,6,61,Spectre of the Gun,Vincent McEveety,"October 25, 1968",56 63 | 3,7,62,Day of the Dove,Marvin Chomsky,"November 1, 1968",66 64 | 3,8,63,For the World Is Hollow and I Have Touched the Sky,Tony Leader,"November 8, 1968",65 65 | 3,9,64,The Tholian Web,Herb Wallerstein,"November 15, 1968",64 66 | 3,10,65,Plato's Stepchildren,David Alexander,"November 22, 1968",67 67 | 3,11,66,Wink of an Eye,Jud Taylor,"November 29, 1968",68 68 | 3,12,67,The Empath,John Erman,"December 6, 1968",63 69 | 3,13,68,Elaan of Troyius,John Meredyth Lucas,"December 20, 1968",57 70 | 3,14,69,Whom Gods Destroy,Herb Wallerstein,"January 3, 1969",71 71 | 3,15,70,Let That Be Your Last Battlefield,Jud Taylor,"January 10, 1969",70 72 | 3,16,71,The Mark of Gideon,Jud Taylor,"January 17, 1969",72 73 | 3,17,72,That Which Survives,Herb Wallerstein,"January 24, 1969",69 74 | 3,18,73,The Lights of Zetar,Herb Kenwith,"January 31, 1969",73 75 | 3,19,74,Requiem for Methuselah,Murray Golden,"February 14, 1969",76 76 | 3,20,75,The Way to Eden,David Alexander,"February 21, 1969",75 77 | 3,21,76,The Cloud Minders,Jud Taylor,"February 28, 1969",74 78 | 3,22,77,The Savage Curtain,Herschel Daugherty,"March 7, 1969",77 79 | 3,23,78,All Our Yesterdays,Marvin Chomsky,"March 14, 1969",78 80 | 3,24,79,Turnabout Intruder,Herb Wallerstein,"June 3, 1969",79 81 | -------------------------------------------------------------------------------- /swim-competitions-invitation-example/swim-team-roster-table.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Create and Populate Team Roster Table", 3 | "pipeline": [ 4 | "Create Table", 5 | "Populate Table", 6 | "Test Query" 7 | ], 8 | "segments": { 9 | "Create Table": { 10 | "class": "createTable", 11 | "config": { 12 | "tableName": "team_roster", 13 | "tableColumns": [ 14 | { 15 | "name": "player_name", 16 | "type": "text", 17 | "nullable": true 18 | }, 19 | { 20 | "name": "birth_date", 21 | "type": "timestamp without time zone", 22 | "nullable": true 23 | }, 24 | { 25 | "name": "phone_number", 26 | "type": "text", 27 | "nullable": true 28 | }, 29 | { 30 | "name": "email_address", 31 | "type": "text", 32 | "nullable": true 33 | }, 34 | { 35 | "name": "parent_name", 36 | "type": "text", 37 | "nullable": true 38 | }, 39 | { 40 | "name": "team_name", 41 | "type": "text", 42 | "nullable": true 43 | }, 44 | { 45 | "name": "organization", 46 | "type": "text", 47 | "nullable": true 48 | }, 49 | { 50 | "name": "yr", 51 | "type": "text", 52 | "nullable": true 53 | }, 54 | { 55 | "name": "coach_name", 56 | "type": "text", 57 | "nullable": true 58 | }, 59 | { 60 | "name": "coach_phone_number", 61 | "type": "text", 62 | "nullable": true 63 | } 64 | ], 65 | "drop": false, 66 | "ignoreErrors": false 67 | } 68 | }, 69 | "Populate Table": { 70 | "class": "targetSql", 71 | "config": { 72 | "target": "team_roster", 73 | "sql": "select \t'John Doe'\tplayer_name,\t'2010-05-14'::timestamp(6) without time zone \tbirth_date,\t'555-1234'\tphone_number,\t'john.doe@email.com'\temail_address,\t'Michael Doe'\tparent_name,\t'Thunderbolts'\tteam_name,\t'Midwest State University'\torganization,\t'2025'\tyr,\t'Mark Reynolds'\tcoach_name,\t'555-9999'\tcoach_phone_number \t union all\r\nselect \t'Emma Smith'\tplayer_name,\t'2011-08-22'::timestamp(6) without time zone \tbirth_date,\t'555-5678'\tphone_number,\t'emma.smith@email.com'\temail_address,\t'Sarah Smith'\tparent_name,\t'Thunderbolts'\tteam_name,\t'Midwest State University'\torganization,\t'2025'\tyr,\t'Mark Reynolds'\tcoach_name,\t'555-9999'\tcoach_phone_number \t union all\r\nselect \t'Liam Johnson'\tplayer_name,\t'2009-12-03'::timestamp(6) without time zone \tbirth_date,\t'555-8765'\tphone_number,\t'liam.johnson@email.com'\temail_address,\t'David Johnson'\tparent_name,\t'Thunderbolts'\tteam_name,\t'Midwest State University'\torganization,\t'2025'\tyr,\t'Mark Reynolds'\tcoach_name,\t'555-9999'\tcoach_phone_number \t union all\r\nselect \t'Olivia Brown'\tplayer_name,\t'2012-06-17'::timestamp(6) without time zone \tbirth_date,\t'555-4321'\tphone_number,\t'olivia.brown@email.com'\temail_address,\t'Linda Brown'\tparent_name,\t'Thunderbolts'\tteam_name,\t'Midwest State University'\torganization,\t'2025'\tyr,\t'Mark Reynolds'\tcoach_name,\t'555-9999'\tcoach_phone_number \t union all\r\nselect \t'Ava Wilson'\tplayer_name,\t'2011-03-05'::timestamp(6) without time zone \tbirth_date,\t'555-6789'\tphone_number,\t'ava.wilson@email.com'\temail_address,\t'Patricia Wilson'\tparent_name,\t'Thunderbolts'\tteam_name,\t'Midwest State University'\torganization,\t'2025'\tyr,\t'Mark Reynolds'\tcoach_name,\t'555-9999'\tcoach_phone_number \t union all\r\nselect \t'Noah Davis'\tplayer_name,\t'2010-11-29'::timestamp(6) without time zone \tbirth_date,\t'555-3456'\tphone_number,\t'noah.davis@email.com'\temail_address,\t'James Davis'\tparent_name,\t'Thunderbolts'\tteam_name,\t'Midwest State University'\torganization,\t'2025'\tyr,\t'Mark Reynolds'\tcoach_name,\t'555-9999'\tcoach_phone_number \t union all\r\nselect \t'Ethan Martinez'\tplayer_name,\t'2009-09-10'::timestamp(6) without time zone \tbirth_date,\t'555-9012'\tphone_number,\t'ethan.martinez@email.com'\temail_address,\t'Robert Martinez'\tparent_name,\t'Thunderbolts'\tteam_name,\t'Midwest State University'\torganization,\t'2025'\tyr,\t'Mark Reynolds'\tcoach_name,\t'555-9999'\tcoach_phone_number \t union all\r\nselect \t'Sophia Taylor'\tplayer_name,\t'2012-07-25'::timestamp(6) without time zone \tbirth_date,\t'555-2345'\tphone_number,\t'sophia.taylor@email.com'\temail_address,\t'Jennifer Taylor'\tparent_name,\t'Thunderbolts'\tteam_name,\t'Midwest State University'\torganization,\t'2025'\tyr,\t'Mark Reynolds'\tcoach_name,\t'555-9999'\tcoach_phone_number \t union all\r\nselect \t'Mason Anderson'\tplayer_name,\t'2010-01-15'::timestamp(6) without time zone \tbirth_date,\t'555-7890'\tphone_number,\t'mason.anderson@email.com'\temail_address,\t'William Anderson'\tparent_name,\t'Thunderbolts'\tteam_name,\t'Midwest State University'\torganization,\t'2025'\tyr,\t'Mark Reynolds'\tcoach_name,\t'555-9999'\tcoach_phone_number \t union all\r\nselect \t'Isabella Thomas'\tplayer_name,\t'2011-04-30'::timestamp(6) without time zone \tbirth_date,\t'555-5432'\tphone_number,\t'isabella.thomas@email.com'\temail_address,\t'Elizabeth Thomas'\tparent_name,\t'Thunderbolts'\tteam_name,\t'Midwest State University'\torganization,\t'2025'\tyr,\t'Mark Reynolds'\tcoach_name,\t'555-9999'\tcoach_phone_number \t ", 74 | "append": false, 75 | "ignoreErrors": false 76 | } 77 | }, 78 | "Test Query": { 79 | "class": "extractData", 80 | "config": { 81 | "selectQuery": "select\r\n player_name\r\n ,birth_date\r\n ,phone_number\r\n ,email_address\r\n ,parent_name\r\n ,team_name\r\n ,organization\r\n ,yr\r\n ,coach_name\r\n ,coach_phone_number\r\nfrom \r\n custom_insights.team_roster ", 82 | "bulkLoadEnabled": true, 83 | "ignoreErrors": false 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /BETA-encrypt-student-worker-data/README.md: -------------------------------------------------------------------------------- 1 | # Encrypt Data Fitting Example: PGP Encryption Pipeline 2 | 3 | This repository provides sample pipelines demonstrating how to use the **Encrypt Data** fitting in Data Connect to apply PGP encryption to sensitive data before transferring it to external systems. 4 | 5 | **The Encrypt Data fitting enables secure, standards-compliant encryption of data within your pipelines.** It supports PGP (Pretty Good Privacy) encryption with optional digital signatures, making it ideal for protecting sensitive information during data transfers to external partners, government agencies, or third-party systems. 6 | 7 | In this example, the pipeline extracts student worker records from Banner, formats the data as CSV, encrypts it using the Encrypt Data fitting with PGP, and uploads the encrypted file to an SFTP server. 8 | 9 | --- 10 | 11 | ## Encrypt Data Fitting Overview 12 | 13 | The `encryptData` fitting provides PGP encryption capabilities with the following features: 14 | 15 | ### Key Features 16 | 17 | | Feature | Description | 18 | |---------|-------------| 19 | | **PGP Encryption** | Encrypts data using a PGP public key, ensuring only the intended recipient can decrypt it | 20 | | **Digital Signatures** | Optionally signs the encrypted data with a private key to verify authenticity and integrity | 21 | | **Binary Output** | Produces binary encrypted output suitable for file transfer | 22 | | **Error Handling** | Configurable error handling with the `ignoreErrors` option | 23 | 24 | ## Pipelines Included 25 | 26 | * `encrypt-student-worker-data` – Extracts real student worker data from Banner, encrypts it using the Encrypt Data fitting, and uploads it to SFTP. 27 | * `encrypt-student-worker-data-mock` – Uses mock data to demonstrate the Encrypt Data fitting without requiring a real connection. 28 | 29 | --- 30 | 31 | ## Prerequisites 32 | 33 | You will need: 34 | 35 | * Experience Premium with Integration Designer 36 | * Insights with the Extensibility license (for the Extract Data fitting) 37 | * **PGP key pair** for encryption and signing (public key, private key, and passphrase) 38 | * SFTP server credentials (private key, username, host, and destination path) 39 | 40 | ## Pipeline Details 41 | 42 | ### `encrypt-student-worker-data` 43 | 44 | This is the production pipeline that performs the following steps: 45 | 46 | 1. **Extract Data** – Queries Banner for active student worker records including employee details, position information, and salary data. 47 | 2. **Delimited Formatter** – Formats the extracted data into a CSV structure with selected columns. 48 | 3. **File Writer** – Stores the formatted CSV in S3 temporary storage. 49 | 4. **File Reader** – Reads the CSV file from temporary storage for encryption. 50 | 5. **Encrypt Data** – **Encrypts the file using PGP encryption and signs it with a digital signature.** 51 | 6. **SFTP Put** – Securely uploads the encrypted GPG file to the configured SFTP server. 52 | 53 | ### `encrypt-student-worker-data-mock` 54 | 55 | This is a test version of the pipeline that: 56 | 57 | * Generates synthetic student worker data using a JavaScript Transform fitting 58 | * Demonstrates the full Encrypt Data fitting workflow with mock data 59 | * Is useful for testing encryption configuration without affecting real data 60 | 61 | --- 62 | 63 | ## Parameters 64 | 65 | | Parameter | Description | 66 | |-----------|-------------| 67 | | `publicKeyEncrypt` | The PGP public key used to encrypt the data file | 68 | | `privateKeyEncrypt` | The PGP private key used to sign the encrypted file for authenticity verification | 69 | | `passphraseEncrypt` | The passphrase associated with the PGP private key used for signing | 70 | | `privateKeySFTP` | The private key used for authenticating with the SFTP server | 71 | | `sftpUsername` | The username for authenticating with the SFTP server | 72 | | `sftpHost` | The hostname or IP address of the SFTP server | 73 | | `sftpFilePath` | The full file path on the SFTP server where the encrypted file will be uploaded | 74 | 75 | --- 76 | 77 | ## Execution Instructions 78 | 79 | ### For the Mock Pipeline 80 | 81 | 1. Use the **Test** button in Integration Designer to simulate the entire workflow with mock data. 82 | 2. Provide valid PGP keys (public key, private key, and passphrase) to test the Encrypt Data fitting. 83 | 3. Provide SFTP credentials to complete the file upload. 84 | 4. Verify the encrypted `.gpg` file is successfully uploaded to the SFTP server. 85 | 86 | ### For the Production Pipeline 87 | 88 | 1. Ensure you have access to the Banner database with student worker data. 89 | 2. **Publish** the pipeline using Integration Designer. 90 | 3. Create a job in **Integration Packages** and provide values for all parameters. 91 | 4. Run the job to extract, encrypt, and upload the student worker data. 92 | 93 | --- 94 | 95 | ## Security Considerations 96 | 97 | The Encrypt Data fitting provides multiple layers of security: 98 | 99 | * **PGP Encryption**: Uses asymmetric encryption where data is encrypted with a public key and can only be decrypted with the corresponding private key held by the recipient. 100 | * **Digital Signatures**: When `signature: true`, the fitting signs the data with your private key, allowing recipients to verify the data's authenticity and that it hasn't been tampered with. 101 | * **Sensitive Parameters**: All encryption keys and credentials should be marked as `sensitive: true` in parameters to ensure they are stored securely and not logged. 102 | * **Secure Transfer**: Combined with SFTP, provides end-to-end security for data in transit. 103 | 104 | --- 105 | 106 | ## Other Use Cases for Encrypt Data Fitting 107 | 108 | The Encrypt Data fitting is essential for scenarios requiring secure data transfer: 109 | 110 | * **Payroll exports** to external payroll providers 111 | * **Financial data transfers** to auditing firms 112 | * **HR data synchronization** with third-party HR systems 113 | * **Student records exports** for verification services 114 | * **Vendor data exchanges** requiring encryption compliance 115 | 116 | --- 117 | 118 | ## File Output 119 | 120 | The pipeline generates a PGP-encrypted `.gpg` file containing the student worker data in CSV format. The file is uploaded to the specified SFTP location with the configured filename. 121 | -------------------------------------------------------------------------------- /swim-competitions-invitation-example/README.md: -------------------------------------------------------------------------------- 1 | # Personalized HTML File Generation and Email Pipelines 2 | 3 | This repository contains a set of sample pipelines that demonstrate how to use Data Connect to generate personalized HTML files and send them as email attachments. The approach supports one-to-many relationships (e.g., one player with multiple competitions) and is suitable for personalized communications across various domains. 4 | 5 | In this demonstration, we use a swim team scenario as the main example, where each player receives a custom HTML invitation listing their assigned competitions. However, this structure can be easily adapted for other use cases requiring similar personalization. 6 | 7 | ## Pipelines Included 8 | 9 | * `swim-team-roster-table` – Creates the `team_roster` table to store player details (name, contact info, etc.) and populates it with sample data. 10 | * `swim-team-competitions-table` – Creates the `player_competitions` table to store which competitions each player is assigned to and populate with sample data. (1 to many relationships are set here since each player can be related to any number of competitions). 11 | * `swim-team-send-player-invitations` – Pipeline that generates personalized HTML invitations based on actual team and competition data, stores the file, and emails it to each player. 12 | * `swim-team-send-player-invitations-mock` – Pipeline that simulates the entire process using generated test data for development or testing purposes. 13 | 14 | --- 15 | 16 | ## Prerequisites 17 | 18 | You must have the following: 19 | 20 | * Experience Premium with Integration Designer 21 | * Insights with the Extensibility license to create custom transformations 22 | * Access to real or test data for team members and their associated competitions 23 | * Verified email that will be used to send emails. 24 | 25 | --- 26 | 27 | ## Pipeline Details 28 | 29 | ### **1. `swim-team-roster-table`** 30 | Creates the `team_roster` table with fields such as player name, email address, and team metadata. It also populates the table with example data to support testing or demonstration. 31 | 32 | ### **2. `swim-team-competitions-table`** 33 | Creates the `player_competitions` table with player-to-competition mappings. It demonstrates a one-to-many relationship, where each player may have multiple assigned competitions. The pipeline also populates this table with sample entries. 34 | 35 | ### **3. `swim-team-send-player-invitations`** 36 | This is the main production pipeline that: 37 | 38 | - Joins player and competition data from `team_roster` and `player_competitions` 39 | - Aggregates competitions per player 40 | - Formats data using an HTML template 41 | - Saves the file to a storage location (e.g., S3) 42 | - Emails each player their invitation with the HTML attached 43 | 44 | **Email Template Example**: 45 | - Player’s name 46 | - List of competitions 47 | - Event dates and details 48 | - Optional footer with coach or team info 49 | 50 | ### **4. `swim-team-send-player-invitations-mock`** 51 | This is a fully functional test version of the real pipeline. It: 52 | 53 | - Generates synthetic players and competitions 54 | - Mimics the aggregation, HTML formatting, and email sending logic 55 | - Is useful for end-to-end testing or development without affecting real users 56 | 57 | --- 58 | 59 | ## Execution Instructions 60 | 61 | For table-creation pipelines (`swim-team-roster-table` and `swim-team-competitions-table`): 62 | 63 | - Use the **Test** button in Integration Designer to create and populate the database artifacts. 64 | 65 | For email pipelines: 66 | 67 | - **Mock pipeline**: Use the **Test** button to simulate and verify email generation with mock data. 68 | - **Real pipeline**: Populate the tables with real data, then **Publish** the pipeline and run it from the **Integration Packages** interface. Emails will be sent to actual recipients with personalized attachments. 69 | 70 | --- 71 | 72 | ## Email Details 73 | 74 | **Subject**: 75 | `You're Invited – {{message.header.player.name}}` 76 | 77 | **Body**: 78 | Includes: 79 | - Greeting and context 80 | - Player-specific information 81 | - Summary of competitions 82 | - Optional call to action 83 | 84 | **Attachment**: 85 | Personalized HTML file with the player's competition assignments with dynamic details. 86 | 87 | --- 88 | 89 | ## Pipeline Outputs 90 | 91 | Below are some sample outputs generated by the pipeline. 92 | 93 | ![Pipeline Output 1 - Email Sent](../docs/Swim%20Competition%20Invitation%20Email%20Output%20Sample.png) 94 | ![Pipeline Output 2 - HTML Generated Invitation File](../docs/Swim%20Competition%20Invitation%20Leter%20Output%20Sample.png) 95 | 96 | --- 97 | 98 | ## Other Use Cases 99 | 100 | This pipeline pattern is particularly useful in higher education settings where personalized documents must reflect **one-to-many relationships**—for example, one student related to many courses, awards, or events. Examples include: 101 | 102 | - **Course Registration Summaries** – One student with many registered courses; generate an individualized schedule including course names, times, and instructors. 103 | - **Academic Advising Reports** – One student with multiple degree requirements or advisor comments; compile all relevant insights into a single report. 104 | - **Financial Aid Notifications** – One student with multiple aid items (grants, loans, scholarships); create a single document summarizing their complete financial aid package. 105 | - **Event Invitations** – One participant invited to several event sessions or activities; send an agenda listing their personalized itinerary. 106 | - **Internship or Career Fair Invitations** – One student matched with several employers or sessions based on major or interest; send a tailored list of meetings or booths to visit. 107 | - **Scholarship Application Status** – One applicant with multiple submitted applications or responses from different scholarship committees; compile all updates in one personalized message. 108 | 109 | --- 110 | 111 | ## Conclusion 112 | 113 | These pipelines provide a reusable and scalable approach for creating and sending personalized HTML content via email. The swim team example demonstrates a clear and extensible use case, and the pattern can be readily adapted for other domains where one-to-many relationships and individualized communication are needed. 114 | 115 | By leveraging Data Connect’s transformation tools and extensibility features, institutions can easily personalize messages with dynamic data, automate complex reporting workflows, and scale communication efforts across various departments and audiences. 116 | -------------------------------------------------------------------------------- /Designer Examples/persons-export.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "persons-export", 3 | "parameters": [ 4 | { 5 | "name": "ethosApiKey", 6 | "type": "string", 7 | "label": "Api Key", 8 | "required": true, 9 | "sensitive": true 10 | } 11 | ], 12 | "pipeline": [ 13 | "Get Persons", 14 | "For Each Person", 15 | "Object Mapper", 16 | "Delimited Formatter", 17 | "Stringify", 18 | "File Writer" 19 | ], 20 | "segments": { 21 | "Get Persons": { 22 | "class": "ethosProxyGet", 23 | "config": { 24 | "resource": "persons", 25 | "acceptVersions": [ 26 | "12" 27 | ], 28 | "cache": false, 29 | "ignoreErrors": false 30 | } 31 | }, 32 | "For Each Person": { 33 | "class": "forEach" 34 | }, 35 | "Object Mapper": { 36 | "class": "objectMapper", 37 | "config": { 38 | "ignoreUndefined": false, 39 | "keepOriginal": true, 40 | "mapping": [ 41 | { 42 | "source": [ 43 | { 44 | "path": "$.names", 45 | "default": "", 46 | "isArray": false 47 | } 48 | ], 49 | "target": "_name", 50 | "transform": [ 51 | { 52 | "name": "array.find", 53 | "params": [ 54 | "preferred", 55 | "$.preference" 56 | ] 57 | } 58 | ] 59 | }, 60 | { 61 | "source": [ 62 | { 63 | "path": "", 64 | "default": "{{mapped._name}}", 65 | "isArray": false 66 | } 67 | ], 68 | "target": "First Name", 69 | "transform": [ 70 | { 71 | "name": "object.get", 72 | "params": [ 73 | "$.firstName" 74 | ] 75 | }, 76 | { 77 | "name": "string.trimTruncate", 78 | "params": [ 79 | "60" 80 | ] 81 | } 82 | ] 83 | }, 84 | { 85 | "source": [ 86 | { 87 | "path": "", 88 | "default": "{{mapped._name}}", 89 | "isArray": false 90 | } 91 | ], 92 | "target": "Middle Name", 93 | "transform": [ 94 | { 95 | "name": "object.get", 96 | "params": [ 97 | "$.middleName" 98 | ] 99 | }, 100 | { 101 | "name": "string.trimTruncate", 102 | "params": [ 103 | "60" 104 | ] 105 | } 106 | ] 107 | }, 108 | { 109 | "source": [ 110 | { 111 | "path": "", 112 | "default": "{{mapped._name}}", 113 | "isArray": false 114 | } 115 | ], 116 | "target": "Last Name", 117 | "transform": [ 118 | { 119 | "name": "object.get", 120 | "params": [ 121 | "$.lastName" 122 | ] 123 | }, 124 | { 125 | "name": "string.trimTruncate", 126 | "params": [ 127 | "60" 128 | ] 129 | } 130 | ] 131 | }, 132 | { 133 | "source": [ 134 | { 135 | "path": "", 136 | "default": "{{mapped._name}}", 137 | "isArray": false 138 | } 139 | ], 140 | "target": "Full Name", 141 | "transform": [ 142 | { 143 | "name": "object.get", 144 | "params": [ 145 | "$.fullName" 146 | ] 147 | }, 148 | { 149 | "name": "string.trimTruncate", 150 | "params": [ 151 | "60" 152 | ] 153 | } 154 | ] 155 | }, 156 | { 157 | "source": [ 158 | { 159 | "path": "$.dateOfBirth", 160 | "default": "", 161 | "isArray": false 162 | } 163 | ], 164 | "target": "Date of Birth", 165 | "transform": [ 166 | { 167 | "name": "date.toISOString" 168 | } 169 | ] 170 | }, 171 | { 172 | "source": [ 173 | { 174 | "path": "$.ethnicity.reporting[0].country.ethnicCategory", 175 | "default": "", 176 | "isArray": false 177 | } 178 | ], 179 | "target": "Ethnicity" 180 | }, 181 | { 182 | "source": [ 183 | { 184 | "path": "$.races[*].reporting[0].country.racialCategory", 185 | "default": "", 186 | "isArray": false 187 | } 188 | ], 189 | "target": "Race", 190 | "transform": [ 191 | { 192 | "name": "array.join", 193 | "params": [ 194 | "|" 195 | ] 196 | } 197 | ] 198 | } 199 | ] 200 | } 201 | }, 202 | "Delimited Formatter": { 203 | "class": "delimitedFormatter", 204 | "config": { 205 | "normalizeColumns": false, 206 | "detail": { 207 | "columns": [ 208 | { 209 | "path": "First Name" 210 | }, 211 | { 212 | "path": "Middle Name" 213 | }, 214 | { 215 | "path": "Last Name" 216 | }, 217 | { 218 | "path": "Full Name" 219 | }, 220 | { 221 | "path": "gender", 222 | "label": "Gender" 223 | }, 224 | { 225 | "path": "Ethnicity" 226 | }, 227 | { 228 | "path": "Race" 229 | }, 230 | { 231 | "path": "Date of Birth" 232 | } 233 | ], 234 | "columnHeaders": true 235 | } 236 | } 237 | }, 238 | "Stringify": { 239 | "class": "stringify" 240 | }, 241 | "File Writer": { 242 | "class": "s3sink", 243 | "config": { 244 | "key": "personsReport.csv", 245 | "addNewlineAfterEachMessage": true, 246 | "includeTotalCount": false, 247 | "generatePresignedUrl": true 248 | } 249 | } 250 | } 251 | } -------------------------------------------------------------------------------- /BETA-encrypt-student-worker-data/encrypt-student-worker-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Encrypt Student Worker Data Pipeline", 3 | "description": "This pipeline extracts student worker data from Banner, formats it as a CSV file, encrypts it using PGP encryption, and securely transfers the encrypted file to an SFTP server.", 4 | "parameters": [ 5 | { 6 | "name": "publicKeyEncrypt", 7 | "label": "PGP Public Key", 8 | "description": "The PGP public key used to encrypt the student worker data file.", 9 | "type": "string", 10 | "required": true, 11 | "sensitive": true, 12 | "multiline": true, 13 | "persistent": true 14 | }, 15 | { 16 | "name": "privateKeyEncrypt", 17 | "label": "PGP Private Key for Signing", 18 | "description": "The PGP private key used to sign the encrypted file for authenticity verification.", 19 | "type": "string", 20 | "required": true, 21 | "sensitive": true, 22 | "multiline": true, 23 | "persistent": true 24 | }, 25 | { 26 | "name": "passphraseEncrypt", 27 | "type": "string", 28 | "label": "PGP Passphrase", 29 | "required": true, 30 | "sensitive": true, 31 | "persistent": true, 32 | "description": "The passphrase associated with the PGP private key used for signing." 33 | }, 34 | { 35 | "name": "privateKeySFTP", 36 | "label": "SFTP Private Key", 37 | "description": "The private key used for authenticating with the SFTP server.", 38 | "type": "string", 39 | "required": true, 40 | "sensitive": true, 41 | "multiline": true, 42 | "persistent": true 43 | }, 44 | { 45 | "name": "sftpUsername", 46 | "label": "SFTP Username", 47 | "description": "The username for authenticating with the SFTP server.", 48 | "type": "string", 49 | "required": true 50 | }, 51 | { 52 | "name": "sftpHost", 53 | "label": "SFTP Host", 54 | "description": "The hostname or IP address of the SFTP server.", 55 | "type": "string", 56 | "required": true 57 | }, 58 | { 59 | "name": "sftpFilePath", 60 | "label": "SFTP Destination File Path", 61 | "description": "The full file path on the SFTP server where the encrypted file will be uploaded.", 62 | "type": "string", 63 | "required": true 64 | } 65 | ], 66 | "pipeline": [ 67 | "Extract Data - Extract Student Worker Data", 68 | "Delimited Formatter - Format Data as CSV", 69 | "S3 Sink - Write CSV to Temporary Storage", 70 | "S3 Accessor - Read CSV File", 71 | "Encrypt Data - Encrypt Data with PGP", 72 | "SFTP Put - Upload Encrypted File to SFTP" 73 | ], 74 | "segments": { 75 | "Extract Data - Extract Student Worker Data": { 76 | "class": "extractData", 77 | "description": "Extracts active student worker records from Banner, including employee details, position information, and salary data.", 78 | "config": { 79 | "selectQuery": "SELECT spriden_id,\r\n NBRJOBS_ORGN_CODE_TS,\r\n PEBEMPL_ORGN_CODE_HOME,\r\n PEBEMPL_ORGN_CODE_DIST,\r\n nbrjobs_posn, NBBPOSN_GRADE,\r\n NBBPOSN_TITLE,\r\n NBBPOSN_EXEMPT_IND,\r\n NBBPOSN_POSN_REPORTS,\r\n NBRJOBS_SAL_GRADE,\r\n nbrjobs_ann_salary,\r\n spriden_last_name,\r\n spriden_first_name,\r\n nbrjobs_suff,\r\n nbrjobs_ecls_code\r\nFROM nbrjobs a,\r\nNBBPOSN, spbpers, pebempl,\r\n spriden\r\nWHERE nbrjobs_pidm = spriden_pidm\r\nand spriden_pidm = pebempl_pidm\r\nand spriden_pidm = spbpers_pidm\r\nand nbrjobs_posn = nbbposn_posn\r\n AND spriden_change_ind IS NULL\r\n AND a.nbrjobs_status <> 'T'\r\nAND a.nbrjobs_ecls_code in ('US','ST','GB','NR')\r\n AND nbrjobs_effective_date =\r\n (SELECT MAX(nbrjobs_effective_date)\r\n FROM nbrjobs b\r\n WHERE nbrjobs_effective_date <= now()\r\n AND b.nbrjobs_pidm = a.nbrjobs_pidm\r\n AND b.nbrjobs_posn = a.nbrjobs_posn\r\n AND b.nbrjobs_suff = a.nbrjobs_suff)\r\nORDER BY spriden_last_name,\r\n spriden_first_name,\r\n a.nbrjobs_posn;", 80 | "bulkLoadEnabled": true, 81 | "ignoreErrors": false 82 | } 83 | }, 84 | "Delimited Formatter - Format Data as CSV": { 85 | "class": "delimitedFormatter", 86 | "description": "Formats the extracted student worker data into a CSV structure with selected columns including name, salary grade, annual salary, position code, and title.", 87 | "config": { 88 | "normalizeColumns": false, 89 | "detail": { 90 | "columns": [ 91 | { 92 | "path": "spriden_first_name" 93 | }, 94 | { 95 | "path": "spriden_last_name" 96 | }, 97 | { 98 | "path": "nbrjobs_sal_grade" 99 | }, 100 | { 101 | "path": "nbrjobs_ann_salary" 102 | }, 103 | { 104 | "path": "nbrjobs_posn" 105 | }, 106 | { 107 | "path": "nbbposn_title" 108 | } 109 | ], 110 | "columnHeaders": true 111 | } 112 | } 113 | }, 114 | "S3 Sink - Write CSV to Temporary Storage": { 115 | "class": "s3sink", 116 | "description": "Writes the formatted CSV data to temporary S3 storage for subsequent encryption processing.", 117 | "config": { 118 | "key": "studentWorkers.csv", 119 | "addNewlineAfterEachMessage": true, 120 | "includeTotalCount": false, 121 | "generatePresignedUrl": true, 122 | "writeImmediately": false, 123 | "utfEncoding": "utf-8" 124 | } 125 | }, 126 | "S3 Accessor - Read CSV File": { 127 | "class": "S3Accessor", 128 | "description": "Reads the CSV file from S3 temporary storage to prepare it for encryption.", 129 | "config": { 130 | "key": "{{context.__pipelineName}}/{{context.__runId}}/studentWorkers.csv", 131 | "binary": false, 132 | "allowConcurrent": false 133 | } 134 | }, 135 | "Encrypt Data - Encrypt Data with PGP": { 136 | "class": "encryptData", 137 | "description": "Encrypts the CSV file content using PGP encryption with the provided public key and signs it with the private key for authenticity.", 138 | "config": { 139 | "publicKey": "publicKeyEncrypt", 140 | "signature": true, 141 | "signingKey": { 142 | "passphrase": "passphraseEncrypt", 143 | "privateKey": "privateKeyEncrypt" 144 | }, 145 | "binary": true, 146 | "ignoreErrors": false 147 | } 148 | }, 149 | "SFTP Put - Upload Encrypted File to SFTP": { 150 | "class": "sftpPut", 151 | "description": "Securely uploads the encrypted GPG file to the configured SFTP server using private key authentication.", 152 | "config": { 153 | "privateKey": "privateKeySFTP", 154 | "username": "{{context.sftpUsername}}", 155 | "host": "{{context.sftpHost}}", 156 | "fileName": "{{context.sftpFilePath}}", 157 | "push": true 158 | } 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /BETA-residency-code-changed-business-event/notify-on-residency-code-changed.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Resd Code Changer Notification", 3 | "parameters": [ 4 | { 5 | "name": "ethosApiKey", 6 | "type": "string", 7 | "sensitive": true 8 | }, 9 | { 10 | "name": "senderEmail", 11 | "label": "Sender Email", 12 | "description": "Email to be uset to notify the student of the change.\nNOTE: This email has to be previously verified to be able to send emails.", 13 | "type": "string", 14 | "required": true, 15 | "regex": { 16 | "expression": "^(([^<>()[\\]\\.,;:\\s@\\\"]+(\\.[^<>()[\\]\\.,;:\\s@\\\"]+)*)|(\\\".+\\\"))@(([^<>()[\\]\\.,;:\\s@\\\"]+\\.)+[^<>()[\\]\\.,;:\\s@\\\"]{2,})$" 17 | } 18 | } 19 | ], 20 | "pipeline": [ 21 | "Ethos Consume Event", 22 | "Ethos Proxy Get Residency Types", 23 | "Filter Residency Type", 24 | "Prepare Information", 25 | "Notify Student", 26 | "Ethos Consume Confirm" 27 | ], 28 | "segments": { 29 | "Ethos Consume Event": { 30 | "class": "ethosConsumeChangeNotifications", 31 | "description": "Consumes the business event from Ethos Integration when a residency code is changed.", 32 | "config": { 33 | "businessEvents": true, 34 | "target": "businessEvent", 35 | "ignoreErrors": false, 36 | "apiKey": "ethosApiKey" 37 | } 38 | }, 39 | "Ethos Proxy Get Residency Types": { 40 | "class": "ethosProxyGet", 41 | "description": "Retrieves all residency types from Banner via Ethos for use in filtering and notification.", 42 | "config": { 43 | "resource": "residency-types", 44 | "acceptVersions": [ 45 | "7" 46 | ], 47 | "target": "residencyTypes", 48 | "cache": false, 49 | "ignoreErrors": false 50 | } 51 | }, 52 | "Filter Residency Type": { 53 | "class": "JavaScriptTransform", 54 | "description": "Filters and matches the incoming residency code from the event to the correct residency type details.", 55 | "config": { 56 | "pushUndefined": true, 57 | "stopOnError": false, 58 | "draft": false, 59 | "code": "function transform (message, context) {\n const {\n residencyTypes,\n businessEvent\n } = message.payload;\n \n const incomingResdCode = businessEvent.content.changeData.sgbstdnResdCode || null;\n\n message.payload.execCond = incomingResdCode !== null;\n \n if(incomingResdCode !== null) {\n message.payload.newResidencyType = residencyTypes.filter(res => res.code === incomingResdCode)[0]\n }\n \n delete message.payload.residencyTypes;\n \n return message;\n}\n" 60 | } 61 | }, 62 | "Prepare Information": { 63 | "class": "JavaScriptTransform", 64 | "description": "Prepares student information (name, email) from the business event data for notification.", 65 | "if": "message.payload.execCond", 66 | "config": { 67 | "pushUndefined": true, 68 | "stopOnError": false, 69 | "draft": false, 70 | "code": "function transform (message, context) {\n message.header.senderEmail = context.get('senderEmail');\n \n const {\n spridenFirstName: firstName,\n spridenLastName: lastName,\n goremalEmailAddress: email\n } = message.payload.businessEvent.content.additionalData;\n\n message.payload.studentInfo = {\n name: `${firstName} ${lastName}`,\n email\n }\n \n return message;\n}" 71 | } 72 | }, 73 | "Notify Student": { 74 | "class": "Notification", 75 | "description": "Sends a notification email to the student about the residency code update.", 76 | "if": "message.payload.execCond", 77 | "config": { 78 | "emailDetails": { 79 | "fromAddress": "Use dynamically generated address", 80 | "dynamicFromAddress": "{{{context.senderEmail}}}", 81 | "toAddresses": [ 82 | "{{{message.payload.studentInfo.email}}}" 83 | ], 84 | "ccAddresses": [], 85 | "bccAddresses": [], 86 | "subject": "Notification of new residency assignment", 87 | "htmlBody": "\n\n\n \n \n Residency Type Updated\n \n\n\n
\n
\n
\n

Ellucian University

\n
\n
\n

Residency Type Update Notification

\n\n

Dear {{message.payload.studentInfo.name}},

\n\n

\n We are writing to inform you that your residency classification has been updated in our records for the upcoming academic term.\n

\n\n
\n New Residency Type: {{message.payload.newResidencyType.title}}\n
\n\n
\n Change to Residency type may impact your registration fee assessment for the term. Please see an advisor for details.\n
\n\n

\n If you have any questions regarding this change, we encourage you to contact your academic advisor or the Office of Records and Registration.\n

\n\n

\n Sincerely,
\n Office of Records and Registration
\n Ellucian University\n

\n
\n\n
\n This is an automated message from the University Residency Management System. For assistance, please contact your academic office.\n
\n
\n
\n\n", 88 | "hasAttachment": false, 89 | "attachments": [], 90 | "userAcknowledgement": true 91 | }, 92 | "target": "emailConfirmation", 93 | "ignoreErrors": false 94 | } 95 | }, 96 | "Ethos Consume Confirm": { 97 | "class": "ethosConsumeConfirm", 98 | "description": "Confirms the processing of the business event back to Ethos Integration.", 99 | "config": { 100 | "lastProcessedId": "{{message.payload.businessEvent.id}}", 101 | "ignoreErrors": false, 102 | "apiKey": "ethosApiKey" 103 | } 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /BETA-assign-student-priority-registration-group-rulesets/README.md: -------------------------------------------------------------------------------- 1 | # Automated Student Priority Registration Group Assignment using Rules Engine 2 | 3 | This repository provides a sample pipeline demonstrating how to use Data Connect's **Run Ruleset fitting** on the **Ellucian SaaS platform through Experience** to automate the assignment of priority registration groups for students based on configurable business rules. The example showcases how sophisticated decision-making workflows and seamless integrations can be triggered by Banner events. 4 | 5 | **The Run Ruleset fitting is the centerpiece of this automation,** allowing you to define complex, multi-criteria business logic without custom code. This empowers institutions to automate complex decision-making processes that would otherwise require manual review and intervention through powerful integration and automation capabilities. 6 | 7 | In this scenario, a business event is triggered when a student's full-time status is changed in Banner. The pipeline consumes this event, gathers relevant student information, and uses the **Run Ruleset fitting** to evaluate the data against predefined rules and assign the student to the appropriate priority registration group for the upcoming term. 8 | 9 | > **Note:** You must have the business event configured in Banner Event Publisher (BEP), a subscriber application set up in Ethos Integration, and access to the Rules Engine in Integration Packages to create and publish rulesets for use with the Run Ruleset fitting. 10 | 11 | --- 12 | 13 | ## Pipelines Included 14 | 15 | * `assign-student-priority-registration-group-rulesets` – Demonstrates the Run Ruleset fitting by consuming full-time status change events, evaluating student data through configurable business rules, and generating a CSV report with priority assignments. 16 | 17 | --- 18 | 19 | ## Prerequisites 20 | 21 | You will need: 22 | 23 | * Experience Premium with Integration Designer 24 | * Integration Packages with Rules Engine access to create and publish rulesets 25 | * Banner system access with Banner Event Publisher (BEP) 26 | * Ethos Integration subscriber application 27 | * Access to academic standing codes, sport codes, and student classification data 28 | 29 | **Key Component**: The Run Ruleset fitting requires a published ruleset from Integration Packages to function. 30 | 31 | --- 32 | 33 | ## The Run Ruleset Fitting in Action 34 | 35 | The pipeline evaluates students against the following priority registration group criteria using the Run Ruleset fitting: 36 | 37 | | Priority Group | Criteria 1 | OR Criteria 2 | 38 | |---|---|---| 39 | | 1 | Full time senior (04) on Dean's list with first class honors (astd 11) | Full time and part time athletes | 40 | | 2 | Full time seniors | Full time and part time veterans | 41 | | 3 | Full time juniors (3rd year) | Part time seniors | 42 | | 4 | Full time sophomores (2nd year) | Part time juniors | 43 | | 5 | Full time freshmen | Part time sophomores | 44 | | 6 | Part time freshmen | | 45 | 46 | --- 47 | 48 | ## Pipeline Details 49 | 50 | ### `assign-student-priority-registration-group-rulesets` 51 | 52 | This pipeline performs the following steps: 53 | 54 | 1. **Data Preparation**: Retrieves academic standing codes and sport activity codes for reference 55 | 2. **Event Consumption**: Consumes the student full-time status change business event 56 | 3. **Student Data Gathering**: Retrieves student GUID and classification information 57 | 4. **Data Transformation**: Filters and prepares student data for rules evaluation 58 | 5. **Rules Evaluation with Run Ruleset Fitting**: Executes the published ruleset against student data to determine priority group assignments 59 | 6. **Report Generation**: Formats results and generates a CSV report with priority assignments 60 | 7. **Event Confirmation**: Confirms the business event processing 61 | 62 | **The Run Ruleset Fitting Configuration:** 63 | - **Fitting Type**: Run Ruleset - the core component that enables rule-based decision making 64 | - **Ruleset Reference**: `FTchangeEvaluateStudentRegistrationGroup` (published from Integration Packages) 65 | - **Data Mapping**: Maps incoming student data to ruleset input elements for evaluation 66 | - **Output Processing**: Captures ruleset decisions and formats them for downstream processing 67 | 68 | **How the Run Ruleset Fitting Works:** 69 | 1. Receives formatted student data from upstream pipeline steps 70 | 2. Maps data elements to the published ruleset's input criteria 71 | 3. Executes the business rules against the student's specific attributes 72 | 4. Returns priority group assignments based on rule evaluation results 73 | 5. Passes results to subsequent pipeline steps for report generation 74 | 75 | **Ruleset Business Logic:** 76 | - Evaluates multiple student attributes: classification, academic standing, sport participation, veteran status, and enrollment status 77 | - Applies complex OR/AND logic across multiple criteria 78 | - Maps student data to appropriate priority registration groups based on configurable business rules 79 | 80 | --- 81 | 82 | ## Execution Instructions 83 | 84 | 1. **Configure the Business Event in BEP:** 85 | Set up a business event in Banner Event Publisher to listen for changes to the full-time indicator field (`SGBSTDN_FULL_PART_IND`) in the `SGBSTDN` table. 86 | 87 | **Additional Data Configuration:** 88 | - **GORGUID**: Include `GORGUID_GUID` to retrieve student person ID 89 | - **SGRSPRT**: Include `SGRSPRT_ACTC_CODE` for sport codes 90 | - **SHRTTRM**: Include `SHRTTRM_ASTD_CODE_DL` and `SHRTTRM_ASTD_CODE_END_OF_TERM` for academic standing 91 | - **SPBPERS**: Include `SPBPERS_VERA_IND` for veteran status 92 | - **SPRIDEN**: Include `SPRIDEN_FIRST_NAME` and `SPRIDEN_LAST_NAME` for student names 93 | 94 | 2. **Create and Publish the Ruleset:** 95 | In Integration Packages, create the `FTchangeEvaluateStudentRegistrationGroup` ruleset with rules that match the priority assignment logic outlined above. **This published ruleset is essential for the Run Ruleset fitting to function.** 96 | 97 | 3. **Subscribe to the Event:** 98 | Set up a subscriber application in Ethos Integration and subscribe to the `student-fulltimestatus-changed` business event. 99 | 100 | 4. **Configure the Run Ruleset Fitting:** 101 | In Integration Designer, add the Run Ruleset fitting to your pipeline and configure it to: 102 | - Reference the published `FTchangeEvaluateStudentRegistrationGroup` ruleset 103 | - Map incoming student data fields to the ruleset's input elements 104 | - Process ruleset outputs for downstream consumption 105 | 106 | 5. **Publish the Pipeline:** 107 | Publish the complete pipeline in Integration Designer. The pipeline requires an Ethos API key parameter for accessing student data. 108 | 109 | 5. **Configure the Job:** 110 | In Integration Packages, create a new job for the pipeline and configure it to be triggered by the business event. 111 | 112 | Once configured, whenever a student's full-time status is changed in Banner, the business event will trigger and the pipeline will automatically use the Run Ruleset fitting to evaluate the student's data and assign them to the appropriate priority registration group based on your published business rules. 113 | 114 | --- 115 | 116 | ## Pipeline Outputs 117 | 118 | The pipeline generates a CSV report (`StudentPriorityAssignmentReport.csv`) containing: 119 | - Student identification information 120 | - Academic standing details 121 | - Sport participation status 122 | - Veteran status 123 | - Assigned priority registration group 124 | 125 | --- 126 | 127 | ## Other Use Cases 128 | 129 | This Run Ruleset fitting pattern is particularly useful in higher education for automating student services workflows, such as: 130 | 131 | - **Academic Probation Management** – Automatically categorize students based on academic performance metrics 132 | - **Financial Aid Prioritization** – Assign students to aid processing queues based on need and status 133 | - **Advising Appointment Scheduling** – Prioritize appointment slots based on student classification and risk factors 134 | - **Graduation Clearance Processing** – Route students through different clearance workflows based on completion status 135 | - **Housing Assignment Priorities** – Determine housing selection order based on student characteristics 136 | 137 | --- 138 | 139 | ## Conclusion 140 | 141 | This example demonstrates how the **Run Ruleset fitting** enables sophisticated, rule-based automation in Banner environments. The platform's integration capabilities allow institutions to seamlessly combine event processing with configurable business logic execution, creating powerful automation that scales to handle thousands of students while maintaining consistency in policy application. 142 | -------------------------------------------------------------------------------- /persons-examples/persons-cn-export.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "persons-cn-export", 3 | "parameters": [ 4 | { 5 | "name": "ethosApiKey", 6 | "type": "string", 7 | "label": "Api Key", 8 | "required": true, 9 | "sensitive": true 10 | }, 11 | { 12 | "name": "fileName", 13 | "type": "string", 14 | "label": "Export File Name", 15 | "default": "persons.csv", 16 | "required": true, 17 | "sensitive": false 18 | } 19 | ], 20 | "pipeline": [ 21 | "setup", 22 | "Consume Change Notification for persons", 23 | "Extract Person", 24 | "mark-cn-for-confirmation", 25 | "Delimited Formatter", 26 | "Stringify", 27 | "File Writer", 28 | "get-last-processed-id", 29 | "Ethos Consume Confirm" 30 | ], 31 | "segments": { 32 | "setup": { 33 | "class": "JavaScriptTransform", 34 | "config": { 35 | "pushUndefined": true, 36 | "stopOnError": false, 37 | "draft": false, 38 | "code": "function transform(message, context) {\n const fileName = context.get('fileName');\n\n const pipelineName = context.get('__pipelineName');\n const runId = context.get('__runId');\n const s3FileName = `${pipelineName}/${runId}/${fileName}`;\n context.set('s3FileName', s3FileName);\n\n console.log(`s3FileName: ${s3FileName}`);\n\n return message;\n}\n" 39 | } 40 | }, 41 | "Consume Change Notification for persons": { 42 | "class": "ethosConsumeChangeNotifications", 43 | "config": { 44 | "resources": [ 45 | { 46 | "name": "persons", 47 | "supportedVersions": [ 48 | "12" 49 | ] 50 | } 51 | ], 52 | "ignoreErrors": false, 53 | "errorPath": "error" 54 | } 55 | }, 56 | "Extract Person": { 57 | "class": "objectMapper", 58 | "config": { 59 | "ignoreUndefined": false, 60 | "keepOriginal": false, 61 | "mapping": [ 62 | { 63 | "source": [ 64 | { 65 | "path": "$.id", 66 | "default": "", 67 | "isArray": false 68 | } 69 | ], 70 | "target": "cnId" 71 | }, 72 | { 73 | "source": [ 74 | { 75 | "path": "$.content.names", 76 | "default": "", 77 | "isArray": false 78 | } 79 | ], 80 | "target": "_name", 81 | "transform": [ 82 | { 83 | "name": "array.find", 84 | "params": [ 85 | "preferred", 86 | "$.preference" 87 | ] 88 | } 89 | ] 90 | }, 91 | { 92 | "source": [ 93 | { 94 | "path": "", 95 | "default": "{{mapped._name}}", 96 | "isArray": false 97 | } 98 | ], 99 | "target": "First Name", 100 | "transform": [ 101 | { 102 | "name": "object.get", 103 | "params": [ 104 | "$.firstName" 105 | ] 106 | }, 107 | { 108 | "name": "string.trimTruncate", 109 | "params": [ 110 | "60" 111 | ] 112 | } 113 | ] 114 | }, 115 | { 116 | "source": [ 117 | { 118 | "path": "", 119 | "default": "{{mapped._name}}", 120 | "isArray": false 121 | } 122 | ], 123 | "target": "Middle Name", 124 | "transform": [ 125 | { 126 | "name": "object.get", 127 | "params": [ 128 | "$.middleName" 129 | ] 130 | }, 131 | { 132 | "name": "string.trimTruncate", 133 | "params": [ 134 | "60" 135 | ] 136 | } 137 | ] 138 | }, 139 | { 140 | "source": [ 141 | { 142 | "path": "", 143 | "default": "{{mapped._name}}", 144 | "isArray": false 145 | } 146 | ], 147 | "target": "Last Name", 148 | "transform": [ 149 | { 150 | "name": "object.get", 151 | "params": [ 152 | "$.lastName" 153 | ] 154 | }, 155 | { 156 | "name": "string.trimTruncate", 157 | "params": [ 158 | "60" 159 | ] 160 | } 161 | ] 162 | }, 163 | { 164 | "source": [ 165 | { 166 | "path": "", 167 | "default": "{{mapped._name}}", 168 | "isArray": false 169 | } 170 | ], 171 | "target": "Full Name", 172 | "transform": [ 173 | { 174 | "name": "object.get", 175 | "params": [ 176 | "$.fullName" 177 | ] 178 | }, 179 | { 180 | "name": "string.trimTruncate", 181 | "params": [ 182 | "60" 183 | ] 184 | } 185 | ] 186 | }, 187 | { 188 | "source": [ 189 | { 190 | "path": "$.content.dateOfBirth", 191 | "default": "", 192 | "isArray": false 193 | } 194 | ], 195 | "target": "Date of Birth", 196 | "transform": [ 197 | { 198 | "name": "date.toISOString" 199 | } 200 | ] 201 | }, 202 | { 203 | "source": [ 204 | { 205 | "path": "$.content.ethnicity.reporting[0].country.ethnicCategory", 206 | "default": "", 207 | "isArray": false 208 | } 209 | ], 210 | "target": "Ethnicity" 211 | }, 212 | { 213 | "source": [ 214 | { 215 | "path": "$.content.races[*].reporting[0].country.racialCategory", 216 | "default": "", 217 | "isArray": false 218 | } 219 | ], 220 | "target": "Race", 221 | "transform": [ 222 | { 223 | "name": "array.join", 224 | "params": [ 225 | "|" 226 | ] 227 | } 228 | ] 229 | } 230 | ] 231 | } 232 | }, 233 | "mark-cn-for-confirmation": { 234 | "class": "JavaScriptTransform", 235 | "config": { 236 | "pushUndefined": true, 237 | "stopOnError": false, 238 | "draft": false, 239 | "code": "function transform (message, context) {\n const { payload: { cnId } } = message;\n\n context.set(`cnId${cnId}Status`, 'confirm');\n\n return message;\n}\n" 240 | } 241 | }, 242 | "Delimited Formatter": { 243 | "class": "delimitedFormatter", 244 | "config": { 245 | "normalizeColumns": false, 246 | "detail": { 247 | "columns": [ 248 | { 249 | "path": "First Name" 250 | }, 251 | { 252 | "path": "Middle Name" 253 | }, 254 | { 255 | "path": "Last Name" 256 | }, 257 | { 258 | "path": "Full Name" 259 | }, 260 | { 261 | "path": "gender", 262 | "label": "Gender" 263 | }, 264 | { 265 | "path": "Ethnicity" 266 | }, 267 | { 268 | "path": "Race" 269 | }, 270 | { 271 | "path": "Date of Birth" 272 | } 273 | ], 274 | "columnHeaders": true 275 | } 276 | } 277 | }, 278 | "Stringify": { 279 | "class": "stringify" 280 | }, 281 | "File Writer": { 282 | "class": "s3sink", 283 | "config": { 284 | "key": "personsReport.csv", 285 | "addNewlineAfterEachMessage": true, 286 | "includeTotalCount": false, 287 | "generatePresignedUrl": true 288 | } 289 | }, 290 | "get-last-processed-id": { 291 | "class": "JavaScriptTransform", 292 | "config": { 293 | "pushUndefined": true, 294 | "stopOnError": false, 295 | "draft": false, 296 | "code": "function transform(message, context) {\n let lastProcessedId;\n\n for (const key of context.keys()) {\n const match = key.match(/cnId(\\d*)Status/)\n if (match) {\n lastProcessedId = lastProcessedId === undefined ? match[1] : Math.max(lastProcessedId, match[1]);\n }\n }\n\n if (lastProcessedId) {\n context.set('lastProcessedId', lastProcessedId);\n }\n\n return message;\n} " 297 | } 298 | }, 299 | "Ethos Consume Confirm": { 300 | "class": "ethosConsumeConfirm", 301 | "config": { 302 | "lastProcessedId": "{{context.lastProcessedId}}", 303 | "ignoreErrors": false 304 | } 305 | } 306 | } 307 | } -------------------------------------------------------------------------------- /BETA-encrypt-student-worker-data/encrypt-student-worker-data-mock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Encrypt Student Worker Data Pipeline Mock", 3 | "description": "This pipeline demonstrates the encryption and SFTP upload workflow using mock student worker data. It is designed for testing and development purposes without requiring a database connection.", 4 | "parameters": [ 5 | { 6 | "name": "publicKeyEncrypt", 7 | "label": "PGP Public Key", 8 | "description": "The PGP public key used to encrypt the student worker data file.", 9 | "type": "string", 10 | "required": true, 11 | "sensitive": true, 12 | "multiline": true, 13 | "persistent": true 14 | }, 15 | { 16 | "name": "privateKeyEncrypt", 17 | "label": "PGP Private Key for Signing", 18 | "description": "The PGP private key used to sign the encrypted file for authenticity verification.", 19 | "type": "string", 20 | "required": true, 21 | "sensitive": true, 22 | "multiline": true, 23 | "persistent": true 24 | }, 25 | { 26 | "name": "passphraseEncrypt", 27 | "type": "string", 28 | "label": "PGP Passphrase", 29 | "required": true, 30 | "sensitive": true, 31 | "persistent": true, 32 | "description": "The passphrase associated with the PGP private key used for signing." 33 | }, 34 | { 35 | "name": "privateKeySFTP", 36 | "label": "SFTP Private Key", 37 | "description": "The private key used for authenticating with the SFTP server.", 38 | "type": "string", 39 | "required": true, 40 | "sensitive": true, 41 | "multiline": true, 42 | "persistent": true 43 | }, 44 | { 45 | "name": "sftpUsername", 46 | "label": "SFTP Username", 47 | "description": "The username for authenticating with the SFTP server.", 48 | "type": "string", 49 | "required": true 50 | }, 51 | { 52 | "name": "sftpHost", 53 | "label": "SFTP Host", 54 | "description": "The hostname or IP address of the SFTP server.", 55 | "type": "string", 56 | "required": true 57 | }, 58 | { 59 | "name": "sftpFilePath", 60 | "label": "SFTP Destination File Path", 61 | "description": "The full file path on the SFTP server where the encrypted file will be uploaded.", 62 | "type": "string", 63 | "required": true 64 | } 65 | ], 66 | "pipeline": [ 67 | "JavaScript Transform - Generate Mock Student Worker Data", 68 | "For Each - Iterate Student Workers", 69 | "Delimited Formatter - Format Data as CSV", 70 | "S3 Sink - Write CSV to Temporary Storage", 71 | "S3 Accessor - Read CSV File", 72 | "Encrypt Data - Encrypt Data with PGP", 73 | "SFTP Put - Upload Encrypted File to SFTP" 74 | ], 75 | "segments": { 76 | "JavaScript Transform - Generate Mock Student Worker Data": { 77 | "class": "JavaScriptTransform", 78 | "description": "Generates mock student worker data for testing the encryption and SFTP upload workflow without requiring a database connection.", 79 | "config": { 80 | "pushUndefined": true, 81 | "stopOnError": false, 82 | "draft": false, 83 | "code": "function transform(message, context) {\n message.payload.studentWorkers = [\n {\n \"spriden_id\": \"STU001\",\n \"spriden_first_name\": \"John\",\n \"spriden_last_name\": \"Smith\",\n \"nbrjobs_sal_grade\": \"A1\",\n \"nbrjobs_ann_salary\": 15000,\n \"nbrjobs_posn\": \"SW0001\",\n \"nbbposn_title\": \"Library Assistant\",\n \"nbrjobs_orgn_code_ts\": \"LIB01\",\n \"pebempl_orgn_code_home\": \"ACAD\",\n \"pebempl_orgn_code_dist\": \"MAIN\",\n \"nbbposn_grade\": \"01\",\n \"nbbposn_exempt_ind\": \"N\",\n \"nbbposn_posn_reports\": \"MGR001\",\n \"nbrjobs_suff\": \"00\",\n \"nbrjobs_ecls_code\": \"ST\"\n },\n {\n \"spriden_id\": \"STU002\",\n \"spriden_first_name\": \"Emily\",\n \"spriden_last_name\": \"Johnson\",\n \"nbrjobs_sal_grade\": \"A2\",\n \"nbrjobs_ann_salary\": 16500,\n \"nbrjobs_posn\": \"SW0002\",\n \"nbbposn_title\": \"Research Assistant\",\n \"nbrjobs_orgn_code_ts\": \"RES01\",\n \"pebempl_orgn_code_home\": \"GRAD\",\n \"pebempl_orgn_code_dist\": \"MAIN\",\n \"nbbposn_grade\": \"02\",\n \"nbbposn_exempt_ind\": \"N\",\n \"nbbposn_posn_reports\": \"MGR002\",\n \"nbrjobs_suff\": \"00\",\n \"nbrjobs_ecls_code\": \"US\"\n },\n {\n \"spriden_id\": \"STU003\",\n \"spriden_first_name\": \"Michael\",\n \"spriden_last_name\": \"Williams\",\n \"nbrjobs_sal_grade\": \"A1\",\n \"nbrjobs_ann_salary\": 14500,\n \"nbrjobs_posn\": \"SW0003\",\n \"nbbposn_title\": \"IT Help Desk\",\n \"nbrjobs_orgn_code_ts\": \"ITS01\",\n \"pebempl_orgn_code_home\": \"TECH\",\n \"pebempl_orgn_code_dist\": \"MAIN\",\n \"nbbposn_grade\": \"01\",\n \"nbbposn_exempt_ind\": \"N\",\n \"nbbposn_posn_reports\": \"MGR003\",\n \"nbrjobs_suff\": \"00\",\n \"nbrjobs_ecls_code\": \"ST\"\n },\n {\n \"spriden_id\": \"STU004\",\n \"spriden_first_name\": \"Sarah\",\n \"spriden_last_name\": \"Davis\",\n \"nbrjobs_sal_grade\": \"B1\",\n \"nbrjobs_ann_salary\": 18000,\n \"nbrjobs_posn\": \"SW0004\",\n \"nbbposn_title\": \"Lab Technician\",\n \"nbrjobs_orgn_code_ts\": \"SCI01\",\n \"pebempl_orgn_code_home\": \"STEM\",\n \"pebempl_orgn_code_dist\": \"NORTH\",\n \"nbbposn_grade\": \"03\",\n \"nbbposn_exempt_ind\": \"N\",\n \"nbbposn_posn_reports\": \"MGR004\",\n \"nbrjobs_suff\": \"00\",\n \"nbrjobs_ecls_code\": \"GB\"\n },\n {\n \"spriden_id\": \"STU005\",\n \"spriden_first_name\": \"David\",\n \"spriden_last_name\": \"Brown\",\n \"nbrjobs_sal_grade\": \"A2\",\n \"nbrjobs_ann_salary\": 15500,\n \"nbrjobs_posn\": \"SW0005\",\n \"nbbposn_title\": \"Administrative Assistant\",\n \"nbrjobs_orgn_code_ts\": \"ADM01\",\n \"pebempl_orgn_code_home\": \"ADMIN\",\n \"pebempl_orgn_code_dist\": \"MAIN\",\n \"nbbposn_grade\": \"02\",\n \"nbbposn_exempt_ind\": \"N\",\n \"nbbposn_posn_reports\": \"MGR005\",\n \"nbrjobs_suff\": \"00\",\n \"nbrjobs_ecls_code\": \"NR\"\n },\n {\n \"spriden_id\": \"STU006\",\n \"spriden_first_name\": \"Jennifer\",\n \"spriden_last_name\": \"Martinez\",\n \"nbrjobs_sal_grade\": \"A1\",\n \"nbrjobs_ann_salary\": 14000,\n \"nbrjobs_posn\": \"SW0006\",\n \"nbbposn_title\": \"Tutoring Center Assistant\",\n \"nbrjobs_orgn_code_ts\": \"TUT01\",\n \"pebempl_orgn_code_home\": \"ACAD\",\n \"pebempl_orgn_code_dist\": \"MAIN\",\n \"nbbposn_grade\": \"01\",\n \"nbbposn_exempt_ind\": \"N\",\n \"nbbposn_posn_reports\": \"MGR006\",\n \"nbrjobs_suff\": \"00\",\n \"nbrjobs_ecls_code\": \"ST\"\n },\n {\n \"spriden_id\": \"STU007\",\n \"spriden_first_name\": \"Christopher\",\n \"spriden_last_name\": \"Garcia\",\n \"nbrjobs_sal_grade\": \"B2\",\n \"nbrjobs_ann_salary\": 19000,\n \"nbrjobs_posn\": \"SW0007\",\n \"nbbposn_title\": \"Graduate Teaching Assistant\",\n \"nbrjobs_orgn_code_ts\": \"ENG01\",\n \"pebempl_orgn_code_home\": \"GRAD\",\n \"pebempl_orgn_code_dist\": \"SOUTH\",\n \"nbbposn_grade\": \"04\",\n \"nbbposn_exempt_ind\": \"Y\",\n \"nbbposn_posn_reports\": \"MGR007\",\n \"nbrjobs_suff\": \"00\",\n \"nbrjobs_ecls_code\": \"GB\"\n },\n {\n \"spriden_id\": \"STU008\",\n \"spriden_first_name\": \"Amanda\",\n \"spriden_last_name\": \"Wilson\",\n \"nbrjobs_sal_grade\": \"A1\",\n \"nbrjobs_ann_salary\": 13500,\n \"nbrjobs_posn\": \"SW0008\",\n \"nbbposn_title\": \"Recreation Center Staff\",\n \"nbrjobs_orgn_code_ts\": \"REC01\",\n \"pebempl_orgn_code_home\": \"ATHL\",\n \"pebempl_orgn_code_dist\": \"MAIN\",\n \"nbbposn_grade\": \"01\",\n \"nbbposn_exempt_ind\": \"N\",\n \"nbbposn_posn_reports\": \"MGR008\",\n \"nbrjobs_suff\": \"00\",\n \"nbrjobs_ecls_code\": \"ST\"\n },\n {\n \"spriden_id\": \"STU009\",\n \"spriden_first_name\": \"Daniel\",\n \"spriden_last_name\": \"Anderson\",\n \"nbrjobs_sal_grade\": \"A2\",\n \"nbrjobs_ann_salary\": 17000,\n \"nbrjobs_posn\": \"SW0009\",\n \"nbbposn_title\": \"Media Services Assistant\",\n \"nbrjobs_orgn_code_ts\": \"MED01\",\n \"pebempl_orgn_code_home\": \"COMM\",\n \"pebempl_orgn_code_dist\": \"MAIN\",\n \"nbbposn_grade\": \"02\",\n \"nbbposn_exempt_ind\": \"N\",\n \"nbbposn_posn_reports\": \"MGR009\",\n \"nbrjobs_suff\": \"00\",\n \"nbrjobs_ecls_code\": \"US\"\n },\n {\n \"spriden_id\": \"STU010\",\n \"spriden_first_name\": \"Jessica\",\n \"spriden_last_name\": \"Taylor\",\n \"nbrjobs_sal_grade\": \"A1\",\n \"nbrjobs_ann_salary\": 14500,\n \"nbrjobs_posn\": \"SW0010\",\n \"nbbposn_title\": \"Student Success Center Peer Mentor\",\n \"nbrjobs_orgn_code_ts\": \"SSC01\",\n \"pebempl_orgn_code_home\": \"STAF\",\n \"pebempl_orgn_code_dist\": \"MAIN\",\n \"nbbposn_grade\": \"01\",\n \"nbbposn_exempt_ind\": \"N\",\n \"nbbposn_posn_reports\": \"MGR010\",\n \"nbrjobs_suff\": \"00\",\n \"nbrjobs_ecls_code\": \"ST\"\n }\n ];\n \n return message;\n}\n" 84 | } 85 | }, 86 | "For Each - Iterate Student Workers": { 87 | "class": "forEach", 88 | "description": "Iterates over each student worker record in the mock data collection.", 89 | "config": { 90 | "source": "payload.studentWorkers" 91 | } 92 | }, 93 | "Delimited Formatter - Format Data as CSV": { 94 | "class": "delimitedFormatter", 95 | "description": "Formats the mock student worker data into a CSV structure with selected columns including name, salary grade, annual salary, position code, and title.", 96 | "config": { 97 | "normalizeColumns": false, 98 | "detail": { 99 | "columns": [ 100 | { 101 | "path": "spriden_first_name" 102 | }, 103 | { 104 | "path": "spriden_last_name" 105 | }, 106 | { 107 | "path": "nbrjobs_sal_grade" 108 | }, 109 | { 110 | "path": "nbrjobs_ann_salary" 111 | }, 112 | { 113 | "path": "nbrjobs_posn" 114 | }, 115 | { 116 | "path": "nbbposn_title" 117 | } 118 | ], 119 | "columnHeaders": true 120 | } 121 | } 122 | }, 123 | "S3 Sink - Write CSV to Temporary Storage": { 124 | "class": "s3sink", 125 | "description": "Writes the formatted CSV data to temporary S3 storage for subsequent encryption processing.", 126 | "config": { 127 | "key": "studentWorkers.csv", 128 | "addNewlineAfterEachMessage": true, 129 | "includeTotalCount": false, 130 | "generatePresignedUrl": true, 131 | "writeImmediately": false, 132 | "utfEncoding": "utf-8" 133 | } 134 | }, 135 | "S3 Accessor - Read CSV File": { 136 | "class": "S3Accessor", 137 | "description": "Reads the CSV file from S3 temporary storage to prepare it for encryption.", 138 | "config": { 139 | "key": "{{context.__pipelineName}}/{{context.__runId}}/studentWorkers.csv", 140 | "binary": false, 141 | "allowConcurrent": false 142 | } 143 | }, 144 | "Encrypt Data - Encrypt Data with PGP": { 145 | "class": "encryptData", 146 | "description": "Encrypts the CSV file content using PGP encryption with the provided public key and signs it with the private key for authenticity.", 147 | "config": { 148 | "publicKey": "publicKeyEncrypt", 149 | "signature": true, 150 | "signingKey": { 151 | "passphrase": "passphraseEncrypt", 152 | "privateKey": "privateKeyEncrypt" 153 | }, 154 | "binary": true, 155 | "ignoreErrors": false 156 | } 157 | }, 158 | "SFTP Put - Upload Encrypted File to SFTP": { 159 | "class": "sftpPut", 160 | "description": "Securely uploads the encrypted GPG file to the configured SFTP server using private key authentication.", 161 | "config": { 162 | "privateKey": "privateKeySFTP", 163 | "username": "{{context.sftpUsername}}", 164 | "host": "{{context.sftpHost}}", 165 | "fileName": "{{context.sftpFilePath}}", 166 | "push": true 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /swim-competitions-invitation-example/swim-team-send-player-invitations.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Swim Team Invitation Notification Pipeline", 3 | "description": "The pipeline is designed to send competition invitation notifications to swim team players, informing them of their participation in an upcoming event.", 4 | "parameters": [ 5 | { 6 | "name": "coachEmail", 7 | "type": "string", 8 | "label": "Coach Email Address - Sender", 9 | "regex": { 10 | "expression": "^(([^<>()[\\]\\.,;:\\s@\\\"]+(\\.[^<>()[\\]\\.,;:\\s@\\\"]+)*)|(\\\".+\\\"))@(([^<>()[\\]\\.,;:\\s@\\\"]+\\.)+[^<>()[\\]\\.,;:\\s@\\\"]{2,})$" 11 | }, 12 | "required": true, 13 | "description": "Email address of the coach that will be sending invitation notifications to swim team players. NOTE: This email has to be previously verified to be able to send emails." 14 | } 15 | ], 16 | "pipeline": [ 17 | "Extract Swim Team Roster Competitions", 18 | "Accumulate Roster Competitions", 19 | "Group Competitions for Team Players", 20 | "For Each Team Player", 21 | "Prepare Team Member HTML", 22 | "Create Individual Information HTML", 23 | "Create Tem Member Report File", 24 | "Prepare Team Member HTML File", 25 | "Email Swim Competition Invitation File" 26 | ], 27 | "segments": { 28 | "Extract Swim Team Roster Competitions": { 29 | "class": "extractData", 30 | "description": "Extracts swim team roster competitions data from the database.", 31 | "config": { 32 | "selectQuery": "select\r\n roster.player_name\r\n ,roster.birth_date\r\n ,roster.phone_number\r\n ,roster.email_address\r\n ,roster.parent_name\r\n ,roster.team_name\r\n ,roster.organization\r\n ,roster.yr\r\n ,roster.coach_name\r\n ,roster.coach_phone_number\r\n ,competitions.competition\r\nfrom \r\n custom_insights.team_roster as roster\r\njoin (\r\n select distinct player_name\r\n from custom_insights.team_roster\r\n order by player_name\r\n limit 2\r\n) as selected_players\r\n on roster.player_name = selected_players.player_name\r\njoin \r\n custom_insights.team_competitions as competitions \r\n on roster.player_name = competitions.player_name;\r\n", 33 | "bulkLoadEnabled": true, 34 | "ignoreErrors": false 35 | } 36 | }, 37 | "Accumulate Roster Competitions": { 38 | "class": "reducer", 39 | "description": "Aggregates team member records into a roster collection.", 40 | "config": { 41 | "accumulator": "payload.roster" 42 | } 43 | }, 44 | "Group Competitions for Team Players": { 45 | "class": "JavaScriptTransform", 46 | "description": "Groups and aggregates competitions by player.", 47 | "config": { 48 | "pushUndefined": true, 49 | "stopOnError": false, 50 | "draft": false, 51 | "code": "function transform (message, context) {\n const roster = message.payload.roster;\n\n const grouped = {};\n \n roster.forEach(player => {\n const key = player.player_name;\n \n if (!grouped[key]) {\n const { competition, ...rest } = player;\n grouped[key] = {\n ...rest,\n competitions: [competition]\n };\n } else {\n grouped[key].competitions.push(player.competition);\n }\n });\n \n const groupedRoster = Object.values(grouped);\n \n message.payload.roster = groupedRoster;\n \n return message;\n}\n" 52 | } 53 | }, 54 | "For Each Team Player": { 55 | "class": "forEach", 56 | "description": "Iterates over each team player in the roster.", 57 | "config": { 58 | "source": "payload.roster" 59 | } 60 | }, 61 | "Prepare Team Member HTML": { 62 | "class": "JavaScriptTransform", 63 | "description": "Prepares HTML headers with player and coach details for invitation.", 64 | "config": { 65 | "pushUndefined": true, 66 | "stopOnError": false, 67 | "draft": false, 68 | "code": "function transform (message, context) {\n \n const { player_name, \n birth_date,\n phone_number,\n email_address,\n parent_name,\n team_name,\n yr,\n organization,\n coach_name,\n coach_phone_number,\n competitions\n } = message.payload\n \n message.header.fileName = `${player_name} - Invitation.html`\n \n message.header.player = {\n name: player_name,\n birth_date,\n phone_number,\n email_address,\n parent_name,\n competitions\n }\n \n message.header.coach = {\n name: coach_name,\n phone_number: coach_phone_number,\n email_address: context.get('coachEmail')\n };\n \n message.header.general = {\n team_name,\n year: yr,\n organization\n };\n \n return message;\n}\n" 69 | } 70 | }, 71 | "Create Individual Information HTML": { 72 | "class": "htmlFormatter", 73 | "description": "Generates a styled HTML invitation for the team member.", 74 | "config": { 75 | "template": "\r\n\r\n\r\n \r\n \r\n Competition Invitation for {{message.header.player.name}}\r\n \r\n\r\n\r\n
\r\n

Swim Competition Notification

\r\n\r\n

Dear {{message.header.player.parent_name}},

\r\n\r\n

\r\n We are thrilled to inform you that {{message.header.player.name}} has been selected to compete in the upcoming swim competitions representing the {{message.header.general.team_name}}.\r\n

\r\n\r\n

\r\n The events are proudly organized by {{message.header.general.organization}} and will take place during the {{message.header.general.year}} season.\r\n

\r\n\r\n

Participant Details:

\r\n \r\n\r\n

Coach Contact Information:

\r\n \r\n\r\n

Competitions Attending:

\r\n \r\n\r\n

\r\n We are proud of {{message.header.player.name}}'s dedication and commitment, and we look forward to a great performance in the upcoming events.\r\n

\r\n\r\n

\r\n Sincerely,
\r\n Swim Team Administration\r\n

\r\n
\r\n\r\n
\r\n © {{message.header.general.year}} {{message.header.general.organization}}. All rights reserved.\r\n
\r\n\r\n\r\n" 76 | } 77 | }, 78 | "Create Tem Member Report File": { 79 | "class": "s3sink", 80 | "description": "Stores the generated HTML invitation file in S3 with a presigned URL.", 81 | "config": { 82 | "key": "{{message.header.fileName}}", 83 | "addNewlineAfterEachMessage": false, 84 | "includeTotalCount": false, 85 | "generatePresignedUrl": true, 86 | "writeImmediately": true, 87 | "utfEncoding": "utf-8" 88 | } 89 | }, 90 | "Prepare Team Member HTML File": { 91 | "class": "JavaScriptTransform", 92 | "description": "Wraps the HTML invitation content for email attachment compatibility.", 93 | "config": { 94 | "pushUndefined": true, 95 | "stopOnError": false, 96 | "draft": false, 97 | "code": "function transform (message, context) {\n \n message.payload ={\n fileContent: message.payload \n }\n \n return message;\n}\n" 98 | } 99 | }, 100 | "Email Swim Competition Invitation File": { 101 | "class": "Notification", 102 | "description": "Sends the personalized invitation email to the team player.", 103 | "config": { 104 | "emailDetails": { 105 | "subject": "Swim Competition Notification - {{message.header.player.name}}", 106 | "htmlBody": "\n\n \n \n \n \n \n
\n
\n

{{message.header.general.organization}}

\n
\n\n
\n

Dear {{message.header.player.name}},

\n\n

\n Please find attached your official team member information for the upcoming swim competition.\n

\n\n

\n The document includes key details such as your team name ({{message.header.general.team_name}}), season, and your coach’s contact information. We kindly ask that you review this information and notify us of any necessary updates.\n

\n\n

\n We are proud to have you representing our team, and we are confident you will perform with excellence in the competition.\n

\n\n

\n Should you have any questions, please feel free to reach out.\n

\n\n \n \n\n
\n Sincerely,
\n Swim Team Administration
\n {{message.header.general.organization}}\n
\n
\n\n
\n © {{message.header.general.year}} {{message.header.general.organization}}. All rights reserved.\n
\n
\n \n", 107 | "attachments": [ 108 | { 109 | "id": "nbOO7fEFSyWvxf6rCOj4F", 110 | "name": "{{message.header.fileName}}", 111 | "base64Contents": "$.fileContent" 112 | } 113 | ], 114 | "ccAddresses": [], 115 | "fromAddress": "Use dynamically generated address", 116 | "toAddresses": [ 117 | "{{{message.header.player.email_address}}}" 118 | ], 119 | "bccAddresses": [], 120 | "hasAttachment": true, 121 | "dynamicFromAddress": "{{{message.header.coach.email_address}}}", 122 | "userAcknowledgement": true 123 | }, 124 | "ignoreErrors": false 125 | } 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /BETA-create-invoices-in-pdf-from-html/data/style_sc.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 210mm; 3 | height: 297mm; 4 | margin: 0; 5 | padding: auto; 6 | -webkit-print-color-adjust: exact; 7 | } 8 | 9 | .header { 10 | width: 580px; 11 | display: flex; 12 | justify-content: space-between; 13 | } 14 | 15 | .header > img { 16 | width: 150px; 17 | } 18 | 19 | .header .location { 20 | text-align: right; 21 | vertical-align: top; 22 | font-family: arial narrow, helvetica neue, sans-serif; 23 | font-size: 15pt; 24 | } 25 | 26 | .invoice-details { 27 | width: 580px; 28 | display: flex; 29 | align-items: center; 30 | justify-content: space-between; 31 | } 32 | 33 | .margin-left-30 { 34 | margin-left: 30px 35 | } 36 | 37 | .top-left-state-details { 38 | text-align: left; 39 | font-family: arial narrow, helvetica neue, sans-serif; 40 | font-size: 9pt; 41 | } 42 | 43 | /* .top-right-invoice { 44 | 45 | } */ 46 | 47 | .top-right-invoice .head { 48 | text-align: right; 49 | color: rgb(9, 76, 147); 50 | font-family: arial narrow, helvetica neue, sans-serif; 51 | font-size: 15pt; 52 | } 53 | 54 | .top-right-invoice .details { 55 | font-family: arial narrow, helvetica neue, sans-serif; 56 | font-size: 9pt; 57 | border-top: solid rgb(9, 76, 147) 0.5px; 58 | border-bottom: solid rgb(9, 76, 147) 0.5px; 59 | padding: 3pt 0; 60 | margin-top: 3pt; 61 | } 62 | 63 | .top-right-invoice .details > * { 64 | display: flex; 65 | justify-content: space-between; 66 | gap: 5px; 67 | } 68 | 69 | .invoice-main { 70 | width: 600px; 71 | height: 700px; 72 | background-color: rgb(223, 230, 242); 73 | margin-top: 20px; 74 | display: flex; 75 | } 76 | 77 | .invoice-main-left { 78 | height: 100%; 79 | width: 65%; 80 | border-right: 1px solid; 81 | padding: 0 25px; 82 | display: flex; 83 | flex-direction: column; 84 | justify-content: space-evenly; 85 | } 86 | 87 | .invoice-main-right { 88 | height: 100%; 89 | width: 35%; 90 | } 91 | 92 | .invoice-summary-title { 93 | color: rgb(9, 76, 147); 94 | font-family: arial narrow, helvetica neau, sans-serif; 95 | font-size: 21pt; 96 | } 97 | 98 | .invoice-summary-details { 99 | font-family: arial narrow, helvetica neau, sans-serif; 100 | font-size: 12pt; 101 | } 102 | 103 | .invoice-summary-total { 104 | width: 100%; 105 | display: flex; 106 | justify-content: space-between; 107 | font-family: arial narrow, helvetica neau, sans-serif; 108 | font-size: 11pt; 109 | } 110 | 111 | .invoice-summary-required { 112 | width: 100%; 113 | display: flex; 114 | justify-content: space-between; 115 | border-top: rgb(9, 76, 147) 0.5px solid; 116 | margin-top: 3px; 117 | font-family: arial narrow, helvetica neau, sans-serif; 118 | font-size: 11pt; 119 | } 120 | 121 | 122 | .imvoice-main-left-bottom { 123 | font-family: arial narrow, helvetica neau, sans-serif; 124 | font-size: 8pt; 125 | } 126 | 127 | .invoice-main-right-box { 128 | margin: 23px 129 | } 130 | 131 | .invoice-main-right-box-top { 132 | padding: 7px; 133 | background-color: rgb(9, 76, 147); 134 | border-bottom: rgb(255, 255, 255) 0.5px solid; 135 | text-align: center; 136 | font-family: arial narrow, helvetica neau, sans-serif; 137 | font-size: 10pt; 138 | color: rgb(255, 255, 255); 139 | } 140 | 141 | .invoice-main-right-box-bottom { 142 | padding: 7px; 143 | background-color: rgb(9, 76, 147); 144 | vertical-align: top; 145 | text-align: left; 146 | font-family: arial narrow, helvetica neau, sans-serif; 147 | font-size: 18pt; 148 | color: rgb(255, 255, 255); 149 | } 150 | 151 | .fees-details { 152 | width: 580px 153 | } 154 | 155 | .fees-details .title { 156 | color: rgb(9, 76, 147); 157 | font-family: arial narrow, helvetica neue, sans-serif; 158 | font-size: 14pt; 159 | border-bottom: 0.5px solid rgb(9, 76, 147); 160 | padding-bottom: 2px; 161 | } 162 | 163 | .table-head { 164 | min-height: 15px; 165 | margin-top: auto; 166 | font-size: 7pt; 167 | border-bottom: rgb(9, 76, 147) 0.5px solid; 168 | vertical-align: bottom; 169 | display: flex; 170 | flex-direction: row; 171 | justify-content: space-between; 172 | align-items: flex-end; 173 | text-align: center; 174 | font-family: arial narrow, helvetica neau, sans-serif; 175 | } 176 | 177 | .table-head > *:nth-child(1) { 178 | font-size: 8pt; 179 | } 180 | 181 | .table-row-group { 182 | font-size: 8pt; 183 | text-transform: capitalize; 184 | font-family: arial narrow, helvetica neau, sans-serif; 185 | } 186 | 187 | .table-row-group > * { 188 | display: flex; 189 | flex-direction: row; 190 | height: 15px; 191 | vertical-align: bottom; 192 | justify-content: space-between; 193 | align-items: flex-end; 194 | } 195 | 196 | .table-row-group > *:first-child { 197 | font-weight: bold; 198 | color: rgb(9, 76, 147); 199 | } 200 | 201 | .table-row-group > *:last-child { 202 | font-weight: bold; 203 | border-bottom: rgb(9, 76, 147) 0.5px solid; 204 | } 205 | 206 | .table-row-group > * > * { 207 | height: 100%; 208 | display: flex; 209 | align-items: center; 210 | } 211 | 212 | .table-row-group > * > *:nth-child(6), 213 | .table-row-group > * > *:nth-child(5), 214 | .table-row-group > * > *:nth-child(4) { 215 | width: 76px; 216 | text-align: right; 217 | justify-content: right; 218 | /* padding-right: 1px; */ 219 | } 220 | 221 | .table-row-group > * > *:nth-child(3) { 222 | width: 50px; 223 | text-align: center; 224 | justify-content: center; 225 | } 226 | 227 | .table-row-group > * > *:nth-child(2) { 228 | width: 150px; 229 | text-align: center; 230 | justify-content: center; 231 | } 232 | 233 | .table-row-group > * > *:nth-child(1) { 234 | width: 152px; 235 | text-align: left; 236 | justify-content: left; 237 | } 238 | 239 | .table-row-group > * > *:nth-child(4), 240 | .table-row-group > * > *:nth-child(6) { 241 | background-color: rgb(223, 230, 242); 242 | } 243 | 244 | .table-row-group > * > *:nth-child(6) { 245 | border-left: rgb(9, 76, 147) 0.75px solid; 246 | border-right: rgb(9, 76, 147) 0.75px solid; 247 | } 248 | 249 | .table-head > *:nth-child(6), 250 | .table-head > *:nth-child(5), 251 | .table-head > *:nth-child(4) { 252 | width: 76px; 253 | text-align: center; 254 | } 255 | 256 | .table-head > *:nth-child(3) { 257 | width: 50px; 258 | text-align: center; 259 | } 260 | 261 | .table-head > *:nth-child(2) { 262 | width: 150px; 263 | text-align: center; 264 | } 265 | 266 | .table-head > *:nth-child(1) { 267 | width: 152px; 268 | text-align: left; 269 | } 270 | 271 | .footer { 272 | width: 580px; 273 | display: flex; 274 | flex-direction: row; 275 | margin-top: 40px; 276 | } 277 | 278 | .footer > .for-style { 279 | width: 60% 280 | } 281 | 282 | .footer .main { 283 | text-align: left; 284 | font-family: arial narrow, helvetica neue, sans-serif; 285 | font-size: 9pt; 286 | display: flex; 287 | flex-direction: column; 288 | } 289 | 290 | .footer .main > .for-style { 291 | height: 50px; 292 | } 293 | 294 | 295 | /* Templates */ 296 | 297 | .scale-template > svg { 298 | width: 800px; 299 | height: 90px; 300 | font-size: 11px; 301 | overflow: hidden; 302 | } 303 | 304 | /* testamur */ 305 | 306 | .testamur-logo { 307 | width: 6cm; 308 | } 309 | 310 | .testamur-text { 311 | font-family: 'Times New Roman', serif; 312 | font-size: .9cm; 313 | font-style: italic; 314 | text-align: center; 315 | } 316 | 317 | .testamur-bottom p { 318 | text-align: center; 319 | font-style: italic; 320 | /* width: 100%; */ 321 | } 322 | 323 | .transcript { 324 | padding: 0 5mm; 325 | box-sizing: border-box; 326 | } 327 | 328 | .main-transcript { 329 | margin: 0 5mm; 330 | width: 100% - 10mm; 331 | } 332 | 333 | .main-transcript * { 334 | padding: 0; 335 | margin: 0; 336 | } 337 | .transcript-head { 338 | font-weight: bold; 339 | } 340 | 341 | .transcript-head > .transcript-row { 342 | justify-content: flex-end; 343 | } 344 | 345 | .transcript-row { 346 | display: flex; 347 | align-items: center; 348 | } 349 | 350 | .transcript-row > * { 351 | text-align: center; 352 | vertical-align: middle; 353 | } 354 | :root { 355 | --transcript-teaching-period-width: 30mm; 356 | --transcript-credit-points-width: 25mm; 357 | --transcript-mark-width: 15mm; 358 | --transcript-grade-width: 15mm; 359 | --ahegs-table-level-width: 20mm; 360 | --ahegs-table-qualification-type-width: 40mm; 361 | } 362 | .transcript-head > .transcript-row > :nth-child(1) { 363 | width: var(--transcript-teaching-period-width); 364 | } 365 | 366 | .transcript-head > .transcript-row > :nth-child(2) { 367 | width: var(--transcript-credit-points-width); 368 | } 369 | 370 | .transcript-head > .transcript-row > :nth-child(3) { 371 | width: var(--transcript-mark-width); 372 | } 373 | 374 | .transcript-head > .transcript-row > :nth-child(4) { 375 | width: var(--transcript-grade-width); 376 | } 377 | 378 | .transcript-year > .transcript-row > :nth-child(1) { 379 | width: 10mm; 380 | } 381 | 382 | .transcript-group { 383 | margin-left: 10mm; 384 | width: 100% - 10mm; 385 | display: flex; 386 | flex-direction: column; 387 | } 388 | 389 | .transcript-group > .transcript-row { 390 | width: 100%; 391 | display: flex; 392 | } 393 | .transcript-group > .transcript-row >* { 394 | flex-shrink: 0; 395 | } 396 | 397 | .transcript-group > .transcript-row > :nth-child(1) { 398 | width: 30mm; 399 | text-align: left; 400 | } 401 | 402 | .transcript-group > .transcript-row > :nth-child(2) { 403 | width: calc(100% - 30mm - var(--transcript-teaching-period-width) - var(--transcript-credit-points-width) - var(--transcript-mark-width) - var(--transcript-grade-width)); 404 | text-align: left; 405 | } 406 | 407 | .transcript-group > .transcript-row > :nth-child(3) { 408 | width: var(--transcript-teaching-period-width); 409 | } 410 | 411 | .transcript-group > .transcript-row > :nth-child(4) { 412 | width: var(--transcript-credit-points-width); 413 | } 414 | 415 | .transcript-group > .transcript-row > :nth-child(5) { 416 | width: var(--transcript-mark-width); 417 | } 418 | 419 | .transcript-group > .transcript-row > :nth-child(6) { 420 | width: var(--transcript-grade-width); 421 | } 422 | 423 | .transcript-group > .transcript-row > :nth-child(7) { 424 | width: calc(var(--transcript-mark-width) + var(--transcript-grade-width) + 5mm); 425 | right: calc(var(--transcript-mark-width) + var(--transcript-grade-width) + 5mm); 426 | position: relative; 427 | text-align: right; 428 | } 429 | 430 | 431 | 432 | 433 | .ahegs-page { 434 | page-break-before: always; 435 | 436 | width: 210mm; 437 | height: 297mm; 438 | margin: auto; 439 | } 440 | 441 | .ahegs-header { 442 | width: 100%; 443 | height: 25mm; 444 | } 445 | .ahegs-header > img { 446 | height: 100%; 447 | } 448 | .ahegs-main { 449 | background-image: url("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTFTUpc47mpidgkpz4rDfDAQ-PghyvhYU6VcL6Rz1LxsMhCLbaG"); 450 | background-size: cover; 451 | padding: 5mm 10mm; 452 | height: calc(100% - 25mm - 25mm); 453 | width: 100%; 454 | } 455 | .ahegs-table { 456 | width: 100%; 457 | } 458 | 459 | .ahegs-table * { 460 | padding: 0; 461 | margin: 0; 462 | } 463 | 464 | .ahegs-head { 465 | width: 100%; 466 | height: 7mm; 467 | text-align: left; 468 | } 469 | 470 | .ahegs-head .ahegs-row { 471 | display: flex; 472 | flex-direction: row; 473 | justify-content: space-between; 474 | align-items: center; 475 | width: 100%; 476 | height: 100%; 477 | } 478 | 479 | .ahegs-head .ahegs-row > * { 480 | display: flex; 481 | align-items: center; 482 | justify-content: start; 483 | } 484 | .ahegs-head .ahegs-row > :nth-child(1){ 485 | width: var(--ahegs-table-level-width); 486 | background-color: black; 487 | padding-left: 1mm; 488 | color: white; 489 | height: 100%; 490 | } 491 | 492 | .ahegs-head .ahegs-row > :nth-child(2){ 493 | width: calc(100% - var(--ahegs-table-level-width) - var(--ahegs-table-qualification-type-width)); 494 | background-color: #686868; 495 | color: black; 496 | padding-left: 1mm; 497 | height: 100%; 498 | } 499 | 500 | .ahegs-head .ahegs-row > :nth-child(3){ 501 | width: var(--ahegs-table-qualification-type-width); 502 | background-color: #b8b8b8; 503 | color: black; 504 | padding-left: 1mm; 505 | height: 100%; 506 | } 507 | 508 | .ahegs-data { 509 | width: 100%; 510 | } 511 | 512 | .ahegs-data .ahegs-row { 513 | display: flex; 514 | flex-direction: row; 515 | padding-top: 2px; 516 | padding-bottom: 20px; 517 | border-bottom: #cacfd2 2px solid; 518 | } 519 | 520 | .ahegs-data .ahegs-row > *:nth-child(1) { 521 | width: var(--ahegs-table-level-width); 522 | } 523 | .ahegs-data .ahegs-row > *:nth-child(2) { 524 | width: calc(100% - var(--ahegs-table-level-width) - var(--ahegs-table-qualification-type-width)); 525 | } 526 | .ahegs-data .ahegs-row > *:nth-child(3) { 527 | width: var(--ahegs-table-qualification-type-width); 528 | } 529 | 530 | .ahegs-footer { 531 | width: calc(100% - 20px); 532 | height: calc(25mm - 20px); 533 | font-size: x-small; 534 | display: flex; 535 | flex-direction: row; 536 | justify-content: center; 537 | gap: 20px; 538 | padding: 10px; 539 | } 540 | .ahegs-footer > img { 541 | height: 80%; 542 | } 543 | .ahegs-footer > .text { 544 | width: 65%; 545 | } 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | .a4 { 558 | width: 210mm; 559 | height: 297mm; 560 | margin: auto; 561 | } 562 | 563 | .a4p { 564 | width: 210mm; 565 | height: 297mm; 566 | margin: auto; 567 | padding: 2cm; 568 | } 569 | 570 | .color-blue { 571 | color: rgb(9, 76, 147); 572 | } 573 | 574 | .font-size-12 { 575 | font-size: 12pt; 576 | } 577 | 578 | .page-break-after { 579 | break-after: page 580 | } 581 | 582 | .underline { 583 | text-decoration: underline; 584 | } 585 | 586 | .auto-spacing { 587 | text-align: justify; 588 | width: 100%; 589 | } 590 | 591 | .auto-spacing::after { 592 | content: ""; 593 | display: inline-block; 594 | width: 100%; 595 | } 596 | 597 | .uppercase { 598 | text-transform: uppercase; 599 | } 600 | 601 | .flex-row-between { 602 | display: flex; 603 | flex-direction: row; 604 | align-items: center; 605 | justify-content: space-between; 606 | width: 100%; 607 | } 608 | 609 | .flex-col-between-center { 610 | display: flex; 611 | flex-direction: column; 612 | align-items: center; 613 | justify-content: space-between; 614 | } 615 | 616 | .flex-col-between-center > * { 617 | margin-top: 0; 618 | margin-bottom: 0; 619 | } 620 | 621 | 622 | -------------------------------------------------------------------------------- /swim-competitions-invitation-example/swim-team-send-player-invitations-mock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Swim Team Invitation Notification Pipeline Mock", 3 | "description": "The pipeline is designed to send competition invitation notifications to swim team players, informing them of their participation in an upcoming event.", 4 | "parameters": [ 5 | { 6 | "name": "coachEmail", 7 | "label": "Coach Email Address - Sender", 8 | "description": "Email address of the coach that will be sending invitation notifications to swim team players. NOTE: This email has to be previously verified to be able to send emails.", 9 | "type": "string", 10 | "required": true, 11 | "regex": { 12 | "expression": "^(([^<>()[\\]\\.,;:\\s@\\\"]+(\\.[^<>()[\\]\\.,;:\\s@\\\"]+)*)|(\\\".+\\\"))@(([^<>()[\\]\\.,;:\\s@\\\"]+\\.)+[^<>()[\\]\\.,;:\\s@\\\"]{2,})$" 13 | } 14 | } 15 | ], 16 | "pipeline": [ 17 | "Extract Swim Team Roster Competitions Mock", 18 | "Group Competitions for Team Players", 19 | "For Each Team Player", 20 | "Prepare Team Member HTML", 21 | "Create Individual Information HTML", 22 | "Create Tem Member Report File", 23 | "Prepare Team Member HTML File", 24 | "Email Swim Competition Invitation File" 25 | ], 26 | "segments": { 27 | "Extract Swim Team Roster Competitions Mock": { 28 | "class": "JavaScriptTransform", 29 | "description": "Mocks extraction of swim team roster competitions data with player details.", 30 | "config": { 31 | "pushUndefined": true, 32 | "stopOnError": false, 33 | "code": "function transform (message, context) {\n message.payload.roster = [\n {\n \"player_name\": \"John Doe\",\n \"birth_date\": \"2010-05-14 00:00:00\",\n \"phone_number\": \"555-1234\",\n \"email_address\": \"john.doe@email.com\",\n \"parent_name\": \"Michael Doe\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Regional Qualifiers\"\n },\n {\n \"player_name\": \"John Doe\",\n \"birth_date\": \"2010-05-14 00:00:00\",\n \"phone_number\": \"555-1234\",\n \"email_address\": \"john.doe@email.com\",\n \"parent_name\": \"Michael Doe\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"State Championship\"\n },\n {\n \"player_name\": \"John Doe\",\n \"birth_date\": \"2010-05-14 00:00:00\",\n \"phone_number\": \"555-1234\",\n \"email_address\": \"john.doe@email.com\",\n \"parent_name\": \"Michael Doe\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"National Invitational\"\n },\n {\n \"player_name\": \"Emma Smith\",\n \"birth_date\": \"2011-08-22 00:00:00\",\n \"phone_number\": \"555-5678\",\n \"email_address\": \"emma.smith@email.com\",\n \"parent_name\": \"Sarah Smith\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Regional Qualifiers\"\n },\n {\n \"player_name\": \"Emma Smith\",\n \"birth_date\": \"2011-08-22 00:00:00\",\n \"phone_number\": \"555-5678\",\n \"email_address\": \"emma.smith@email.com\",\n \"parent_name\": \"Sarah Smith\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"National Invitational\"\n },\n {\n \"player_name\": \"Emma Smith\",\n \"birth_date\": \"2011-08-22 00:00:00\",\n \"phone_number\": \"555-5678\",\n \"email_address\": \"emma.smith@email.com\",\n \"parent_name\": \"Sarah Smith\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Winter Classic\"\n },\n {\n \"player_name\": \"Liam Johnson\",\n \"birth_date\": \"2009-12-03 00:00:00\",\n \"phone_number\": \"555-8765\",\n \"email_address\": \"liam.johnson@email.com\",\n \"parent_name\": \"David Johnson\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"State Championship\"\n },\n {\n \"player_name\": \"Liam Johnson\",\n \"birth_date\": \"2009-12-03 00:00:00\",\n \"phone_number\": \"555-8765\",\n \"email_address\": \"liam.johnson@email.com\",\n \"parent_name\": \"David Johnson\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Winter Classic\"\n },\n {\n \"player_name\": \"Liam Johnson\",\n \"birth_date\": \"2009-12-03 00:00:00\",\n \"phone_number\": \"555-8765\",\n \"email_address\": \"liam.johnson@email.com\",\n \"parent_name\": \"David Johnson\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Summer Games\"\n },\n {\n \"player_name\": \"Olivia Brown\",\n \"birth_date\": \"2012-06-17 00:00:00\",\n \"phone_number\": \"555-4321\",\n \"email_address\": \"olivia.brown@email.com\",\n \"parent_name\": \"Linda Brown\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Regional Qualifiers\"\n },\n {\n \"player_name\": \"Olivia Brown\",\n \"birth_date\": \"2012-06-17 00:00:00\",\n \"phone_number\": \"555-4321\",\n \"email_address\": \"olivia.brown@email.com\",\n \"parent_name\": \"Linda Brown\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Summer Games\"\n },\n {\n \"player_name\": \"Ava Wilson\",\n \"birth_date\": \"2011-03-05 00:00:00\",\n \"phone_number\": \"555-6789\",\n \"email_address\": \"ava.wilson@email.com\",\n \"parent_name\": \"Patricia Wilson\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"State Championship\"\n },\n {\n \"player_name\": \"Ava Wilson\",\n \"birth_date\": \"2011-03-05 00:00:00\",\n \"phone_number\": \"555-6789\",\n \"email_address\": \"ava.wilson@email.com\",\n \"parent_name\": \"Patricia Wilson\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Spring Tournament\"\n },\n {\n \"player_name\": \"Noah Davis\",\n \"birth_date\": \"2010-11-29 00:00:00\",\n \"phone_number\": \"555-3456\",\n \"email_address\": \"noah.davis@email.com\",\n \"parent_name\": \"James Davis\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Winter Classic\"\n },\n {\n \"player_name\": \"Noah Davis\",\n \"birth_date\": \"2010-11-29 00:00:00\",\n \"phone_number\": \"555-3456\",\n \"email_address\": \"noah.davis@email.com\",\n \"parent_name\": \"James Davis\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"National Invitational\"\n },\n {\n \"player_name\": \"Noah Davis\",\n \"birth_date\": \"2010-11-29 00:00:00\",\n \"phone_number\": \"555-3456\",\n \"email_address\": \"noah.davis@email.com\",\n \"parent_name\": \"James Davis\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Spring Tournament\"\n },\n {\n \"player_name\": \"Noah Davis\",\n \"birth_date\": \"2010-11-29 00:00:00\",\n \"phone_number\": \"555-3456\",\n \"email_address\": \"noah.davis@email.com\",\n \"parent_name\": \"James Davis\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Regional Qualifiers\"\n },\n {\n \"player_name\": \"Ethan Martinez\",\n \"birth_date\": \"2009-09-10 00:00:00\",\n \"phone_number\": \"555-9012\",\n \"email_address\": \"ethan.martinez@email.com\",\n \"parent_name\": \"Robert Martinez\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Summer Games\"\n },\n {\n \"player_name\": \"Ethan Martinez\",\n \"birth_date\": \"2009-09-10 00:00:00\",\n \"phone_number\": \"555-9012\",\n \"email_address\": \"ethan.martinez@email.com\",\n \"parent_name\": \"Robert Martinez\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Regional Qualifiers\"\n },\n {\n \"player_name\": \"Sophia Taylor\",\n \"birth_date\": \"2012-07-25 00:00:00\",\n \"phone_number\": \"555-2345\",\n \"email_address\": \"sophia.taylor@email.com\",\n \"parent_name\": \"Jennifer Taylor\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Spring Tournament\"\n },\n {\n \"player_name\": \"Sophia Taylor\",\n \"birth_date\": \"2012-07-25 00:00:00\",\n \"phone_number\": \"555-2345\",\n \"email_address\": \"sophia.taylor@email.com\",\n \"parent_name\": \"Jennifer Taylor\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"State Championship\"\n },\n {\n \"player_name\": \"Sophia Taylor\",\n \"birth_date\": \"2012-07-25 00:00:00\",\n \"phone_number\": \"555-2345\",\n \"email_address\": \"sophia.taylor@email.com\",\n \"parent_name\": \"Jennifer Taylor\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"National Invitational\"\n },\n {\n \"player_name\": \"Mason Anderson\",\n \"birth_date\": \"2010-01-15 00:00:00\",\n \"phone_number\": \"555-7890\",\n \"email_address\": \"mason.anderson@email.com\",\n \"parent_name\": \"William Anderson\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"National Invitational\"\n },\n {\n \"player_name\": \"Mason Anderson\",\n \"birth_date\": \"2010-01-15 00:00:00\",\n \"phone_number\": \"555-7890\",\n \"email_address\": \"mason.anderson@email.com\",\n \"parent_name\": \"William Anderson\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Winter Classic\"\n },\n {\n \"player_name\": \"Isabella Thomas\",\n \"birth_date\": \"2011-04-30 00:00:00\",\n \"phone_number\": \"555-5432\",\n \"email_address\": \"isabella.thomas@email.com\",\n \"parent_name\": \"Elizabeth Thomas\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Summer Games\"\n },\n {\n \"player_name\": \"Isabella Thomas\",\n \"birth_date\": \"2011-04-30 00:00:00\",\n \"phone_number\": \"555-5432\",\n \"email_address\": \"isabella.thomas@email.com\",\n \"parent_name\": \"Elizabeth Thomas\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Spring Tournament\"\n },\n {\n \"player_name\": \"Isabella Thomas\",\n \"birth_date\": \"2011-04-30 00:00:00\",\n \"phone_number\": \"555-5432\",\n \"email_address\": \"isabella.thomas@email.com\",\n \"parent_name\": \"Elizabeth Thomas\",\n \"team_name\": \"Thunderbolts\",\n \"organization\": \"Midwest State University\",\n \"yr\": \"2025\",\n \"coach_name\": \"Mark Reynolds\",\n \"coach_phone_number\": \"555-9999\",\n \"competition\": \"Regional Qualifiers\"\n }\n ]\n \n return message;\n}\n" 34 | } 35 | }, 36 | "Group Competitions for Team Players": { 37 | "class": "JavaScriptTransform", 38 | "description": "Groups and aggregates competitions by player from the mock data.", 39 | "config": { 40 | "pushUndefined": true, 41 | "stopOnError": false, 42 | "draft": false, 43 | "code": "function transform (message, context) {\n const roster = message.payload.roster;\n\n const grouped = {};\n \n roster.forEach(player => {\n const key = player.player_name;\n \n if (!grouped[key]) {\n const { competition, ...rest } = player;\n grouped[key] = {\n ...rest,\n competitions: [competition]\n };\n } else {\n grouped[key].competitions.push(player.competition);\n }\n });\n \n const groupedRoster = Object.values(grouped);\n \n message.payload.roster = groupedRoster;\n \n return message;\n}\n" 44 | } 45 | }, 46 | "For Each Team Player": { 47 | "class": "forEach", 48 | "description": "Iterates over each team player in the mock roster.", 49 | "config": { 50 | "source": "payload.roster" 51 | } 52 | }, 53 | "Prepare Team Member HTML": { 54 | "class": "JavaScriptTransform", 55 | "description": "Prepares HTML headers and extracts necessary details for the mock invitation.", 56 | "config": { 57 | "pushUndefined": true, 58 | "stopOnError": false, 59 | "draft": false, 60 | "code": "function transform (message, context) {\n \n const { player_name, \n birth_date,\n phone_number,\n email_address,\n parent_name,\n team_name,\n yr,\n organization,\n coach_name,\n coach_phone_number,\n competitions\n } = message.payload\n \n message.header.fileName = `${player_name} - Invitation.html`\n \n message.header.player = {\n name: player_name,\n birth_date,\n phone_number,\n email_address,\n parent_name,\n competitions\n }\n \n message.header.coach = {\n name: coach_name,\n phone_number: coach_phone_number,\n email_address: context.get('coachEmail')\n };\n \n message.header.general = {\n team_name,\n year: yr,\n organization\n };\n \n return message;\n}\n" 61 | } 62 | }, 63 | "Create Individual Information HTML": { 64 | "class": "htmlFormatter", 65 | "description": "Generates a styled HTML invitation for each team member.", 66 | "config": { 67 | "template": "\r\n\r\n\r\n \r\n \r\n Competition Invitation for {{message.header.player.name}}\r\n \r\n\r\n\r\n
\r\n

Swim Competition Notification

\r\n\r\n

Dear {{message.header.player.parent_name}},

\r\n\r\n

\r\n We are thrilled to inform you that {{message.header.player.name}} has been selected to compete in the upcoming swim competitions representing the {{message.header.general.team_name}}.\r\n

\r\n\r\n

\r\n The events are proudly organized by {{message.header.general.organization}} and will take place during the {{message.header.general.year}} season.\r\n

\r\n\r\n

Participant Details:

\r\n \r\n\r\n

Coach Contact Information:

\r\n \r\n\r\n

Competitions Attending:

\r\n \r\n\r\n

\r\n We are proud of {{message.header.player.name}}'s dedication and commitment, and we look forward to a great performance in the upcoming events.\r\n

\r\n\r\n

\r\n Sincerely,
\r\n Swim Team Administration\r\n

\r\n
\r\n\r\n
\r\n © {{message.header.general.year}} {{message.header.general.organization}}. All rights reserved.\r\n
\r\n\r\n\r\n" 68 | } 69 | }, 70 | "Create Tem Member Report File": { 71 | "class": "s3sink", 72 | "description": "Saves the generated HTML invitation mock file to S3 with a presigned URL.", 73 | "config": { 74 | "key": "{{message.header.fileName}}", 75 | "addNewlineAfterEachMessage": false, 76 | "includeTotalCount": false, 77 | "generatePresignedUrl": true, 78 | "writeImmediately": true, 79 | "utfEncoding": "utf-8" 80 | } 81 | }, 82 | "Prepare Team Member HTML File": { 83 | "class": "JavaScriptTransform", 84 | "description": "Wraps the HTML invitation content for email attachment compatibility.", 85 | "config": { 86 | "pushUndefined": true, 87 | "stopOnError": false, 88 | "draft": false, 89 | "code": "function transform (message, context) {\n \n message.payload ={\n fileContent: message.payload \n }\n \n return message;\n}\n" 90 | } 91 | }, 92 | "Email Swim Competition Invitation File": { 93 | "class": "Notification", 94 | "description": "Sends a mock personalized invitation email to the team coach.", 95 | "config": { 96 | "emailDetails": { 97 | "fromAddress": "Use dynamically generated address", 98 | "dynamicFromAddress": "{{{message.header.coach.email_address}}}", 99 | "toAddresses": [ 100 | "{{{message.header.player.email_address}}}" 101 | ], 102 | "ccAddresses": [], 103 | "bccAddresses": [], 104 | "subject": "Swim Competition Notification - {{message.header.player.name}}", 105 | "htmlBody": "\n\n \n \n \n \n \n
\n
\n

{{message.header.general.organization}}

\n
\n\n
\n

Dear {{message.header.player.name}},

\n\n

\n Please find attached your official team member information for the upcoming swim competition.\n

\n\n

\n The document includes key details such as your team name ({{message.header.general.team_name}}), season, and your coach’s contact information. We kindly ask that you review this information and notify us of any necessary updates.\n

\n\n

\n We are proud to have you representing our team, and we are confident you will perform with excellence in the competition.\n

\n\n

\n Should you have any questions, please feel free to reach out.\n

\n\n \n \n\n
\n Sincerely,
\n Swim Team Administration
\n {{message.header.general.organization}}\n
\n
\n\n
\n © {{message.header.general.year}} {{message.header.general.organization}}. All rights reserved.\n
\n
\n \n", 106 | "hasAttachment": true, 107 | "attachments": [ 108 | { 109 | "id": "nbOO7fEFSyWvxf6rCOj4F", 110 | "name": "{{message.header.fileName}}", 111 | "base64Contents": "$.fileContent" 112 | } 113 | ], 114 | "userAcknowledgement": true 115 | }, 116 | "ignoreErrors": false 117 | } 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /BETA-assign-student-priority-registration-group-rulesets/assign-student-priority-registration-group-rulesets.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AssignStudentPriorityRegistrationGroupRulesetsExample1", 3 | "parameters": [ 4 | { 5 | "name": "ethosApiKey", 6 | "type": "string", 7 | "required": true, 8 | "sensitive": true 9 | } 10 | ], 11 | "pipeline": [ 12 | "Ethos Proxy Get Academic Standing/Dean's List Desc", 13 | "Ethos Proxy Get Sport Desc", 14 | "Ethos Consume Event - Get student PF status change", 15 | "Ethos Proxy Get Student GUID", 16 | "Ethos Proxy Get Student Classification", 17 | "JavaScript Transform - Filter Codes for student", 18 | "JavaScript Transform Prepare Info", 19 | "Reducer", 20 | "JavaScript Transform - Format message", 21 | "Run Ruleset", 22 | "For Each Student Evaluation", 23 | "JavaScript Transform Save input/output values", 24 | "Delimited Formatter", 25 | "File Writer", 26 | "Ethos Consume Confirm" 27 | ], 28 | "segments": { 29 | "Ethos Proxy Get Academic Standing/Dean's List Desc": { 30 | "class": "ethosProxyGet", 31 | "config": { 32 | "resource": "academic-standing-codes", 33 | "acceptVersions": [ 34 | "latest" 35 | ], 36 | "target": "academicStandingTypes", 37 | "cache": false, 38 | "ignoreErrors": false 39 | } 40 | }, 41 | "Ethos Proxy Get Sport Desc": { 42 | "class": "ethosProxyGet", 43 | "config": { 44 | "resource": "student-activity-codes", 45 | "acceptVersions": [ 46 | "1" 47 | ], 48 | "target": "sportTypes", 49 | "cache": false, 50 | "ignoreErrors": false 51 | } 52 | }, 53 | "Ethos Consume Event - Get student PF status change": { 54 | "class": "ethosConsumeChangeNotifications", 55 | "config": { 56 | "businessEvents": true, 57 | "target": "businessEvent", 58 | "ignoreErrors": false 59 | } 60 | }, 61 | "Ethos Proxy Get Student GUID": { 62 | "class": "ethosProxyGet", 63 | "config": { 64 | "resource": "students?criteria={\"person\": {\"id\": \"{{{msg.payload.businessEvent.content.additionalData.gorguidGuid}}}\"}}", 65 | "acceptVersions": [ 66 | "latest" 67 | ], 68 | "target": "studentGuidTypes", 69 | "cache": false, 70 | "ignoreErrors": false 71 | } 72 | }, 73 | "Ethos Proxy Get Student Classification": { 74 | "class": "ethosProxyGet", 75 | "config": { 76 | "resource": "student-classifications", 77 | "acceptVersions": [ 78 | "7" 79 | ], 80 | "idFromPayload": "$.studentGuidTypes.0.levelClassifications.0.latestClassification.id", 81 | "target": "studentClassificationTypes", 82 | "cache": false, 83 | "ignoreErrors": false 84 | } 85 | }, 86 | "JavaScript Transform - Filter Codes for student": { 87 | "class": "JavaScriptTransform", 88 | "config": { 89 | "pushUndefined": true, 90 | "stopOnError": false, 91 | "draft": false, 92 | "code": "function transform (message, context) {\n const {\n academicStandingTypes,\n sportTypes,\n studentGuidTypes,\n businessEvent\n } = message.payload;\n \n const incomingAcdCode = businessEvent.content.additionalData.shrttrmAstdCodeEndOfTerm || null;\n message.payload.incomingAcdCode=incomingAcdCode;\n const incomingDlCode = businessEvent.content.additionalData.shrttrmAstdCodeDl || null;\n message.payload.incomingDlCode=incomingDlCode;\n const incomingSportCode = businessEvent.content.additionalData.sgrsprtActcCode || null;\n message.payload.incomingSportCode=incomingSportCode;\n \n\n //filter for academic standing codes\n if(incomingAcdCode !== null) {\n message.payload.newAcdType = academicStandingTypes.filter(acd => acd.code === incomingAcdCode)\n }\n \n //filter for dean's list code where \"deansListInd\": \"Y\"\n if(incomingDlCode !== null) {\n message.payload.newDlType = academicStandingTypes.filter(dl => dl.code === incomingDlCode)\n }\n \n //filter for sport codes\n if(incomingSportCode !== null) {\n message.payload.newSportType = sportTypes.filter(sprt => sprt.code === incomingSportCode)\n }\n \n \n delete message.payload.academicStandingTypes;\n delete message.payload.sportTypes;\n delete message.payload.studentGuidTypes;\n \n return message;\n}\n" 93 | } 94 | }, 95 | "JavaScript Transform Prepare Info": { 96 | "class": "JavaScriptTransform", 97 | "config": { 98 | "pushUndefined": true, 99 | "stopOnError": false, 100 | "draft": false, 101 | "code": "function transform (message, context) {\n // Put your code here.\n\nconst additionalData = message.payload.businessEvent.content.additionalData || {};\n\nmessage.header={\n BusinessEventID: message.payload.businessEvent.id,\n \n}\n\n message.payload = {\n \n FullTimeIndicator: message.payload.businessEvent.content.changeData.sgbstdnFullPartInd || null,\n FirstName: additionalData.spridenFirstName || null,\n LastName: additionalData.spridenLastName || null,\n AstdCodeEndOfTerm: additionalData.shrttrmAstdCodeEndOfTerm || null,\n AstdCodeEndOfTermDesc: message?.payload?.newAcdType?.[0]?.desc ?? null,\n AstdCodeDL: additionalData.shrttrmAstdCodeDl || null,\n AstdCodeDLDesc: message?.payload?.newDlType?.[0]?.desc ?? null,\n SportCode: additionalData.sgrsprtActcCode || null,\n SportCodeDesc: message?.payload?.newSportType?.[0]?.desc ?? null,\n VetCode: additionalData.spbpersVeraInd || null,\n StudentClassificationCode: message.payload.studentClassificationTypes.code || null,\n StudentClassificationDesc: message.payload.studentClassificationTypes.title || null\n \n };\nreturn message;\n}\n" 102 | } 103 | }, 104 | "Reducer": { 105 | "class": "reducer", 106 | "config": { 107 | "accumulator": "payload" 108 | } 109 | }, 110 | "JavaScript Transform - Format message": { 111 | "class": "JavaScriptTransform", 112 | "config": { 113 | "pushUndefined": true, 114 | "stopOnError": false, 115 | "draft": false, 116 | "code": "function transform (message, context) {\n // Put your code here.\n message.header.isArray=message.payload.length>1;\n \n message.payload= {\n //rulesetData:[...message.payload],\n rulesetData:message.payload,\n // isArray:message.payload.length>1\n }\n \n return message;\n}\n" 117 | } 118 | }, 119 | "Run Ruleset": { 120 | "class": "RunRuleset", 121 | "config": { 122 | "ruleset": { 123 | "name": "FTchangeEvaluateStudentRegistrationGroup", 124 | "facts": [ 125 | { 126 | "id": "ee4d4955-ab71-4b62-9f45-e15486d4e890", 127 | "name": "constituent-persons.names.surnames.type.code", 128 | "group": "Advancement", 129 | "dataType": "string", 130 | "displayName": "Constituent Person Name Surname Type Code", 131 | "localization": { 132 | "es": { 133 | "group": "Advancement", 134 | "displayName": "Constituent Person Name Surname Type Code" 135 | }, 136 | "en-AU": { 137 | "group": "Advancement", 138 | "displayName": "Constituent Person Name Surname Type Code" 139 | }, 140 | "en-GB": { 141 | "group": "Advancement", 142 | "displayName": "Constituent Person Name Surname Type Code" 143 | }, 144 | "en-US": { 145 | "group": "Advancement", 146 | "displayName": "Constituent Person Name Surname Type Code" 147 | }, 148 | "fr-CA": { 149 | "group": "Advancement", 150 | "displayName": "Constituent Person Name Surname Type Code" 151 | } 152 | } 153 | }, 154 | { 155 | "id": "a1df9b52-e5d9-47a9-a64d-0d179194f48a", 156 | "name": "constituent-persons.names.personalNames.type.code", 157 | "group": "Advancement", 158 | "dataType": "string", 159 | "displayName": "Constituent Person Name Type Code", 160 | "localization": { 161 | "es": { 162 | "group": "Advancement", 163 | "displayName": "Constituent Person Name Type Code" 164 | }, 165 | "en-AU": { 166 | "group": "Advancement", 167 | "displayName": "Constituent Person Name Type Code" 168 | }, 169 | "en-GB": { 170 | "group": "Advancement", 171 | "displayName": "Constituent Person Name Type Code" 172 | }, 173 | "en-US": { 174 | "group": "Advancement", 175 | "displayName": "Constituent Person Name Type Code" 176 | }, 177 | "fr-CA": { 178 | "group": "Advancement", 179 | "displayName": "Constituent Person Name Type Code" 180 | } 181 | } 182 | }, 183 | { 184 | "id": "b933fd8b-1be0-4367-944c-81467b8900db", 185 | "name": "persons.veteranStatus.detail.code", 186 | "group": "Foundation", 187 | "dataType": "string", 188 | "displayName": "Person Veteran Status Code", 189 | "localization": { 190 | "es": { 191 | "group": "Foundation", 192 | "displayName": "Person Veteran Status Code" 193 | }, 194 | "en-AU": { 195 | "group": "Foundation", 196 | "displayName": "Person Veteran Status Code" 197 | }, 198 | "en-GB": { 199 | "group": "Foundation", 200 | "displayName": "Person Veteran Status Code" 201 | }, 202 | "en-US": { 203 | "group": "Foundation", 204 | "displayName": "Person Veteran Status Code" 205 | }, 206 | "fr-CA": { 207 | "group": "Foundation", 208 | "displayName": "Person Veteran Status Code" 209 | } 210 | } 211 | }, 212 | { 213 | "id": "d412a75c-bb9e-4455-8276-3073021cdecf", 214 | "name": "academicStandingDeansListCode", 215 | "group": "Registration", 216 | "dataType": "string", 217 | "displayName": "Academic Standing-Deans List Code", 218 | "localization": { 219 | "es": { 220 | "group": "Registration", 221 | "displayName": "Academic Standing-Deans List Code" 222 | }, 223 | "en-AU": { 224 | "group": "Registration", 225 | "displayName": "Academic Standing-Deans List Code" 226 | }, 227 | "en-GB": { 228 | "group": "Registration", 229 | "displayName": "Academic Standing-Deans List Code" 230 | }, 231 | "en-US": { 232 | "group": "Registration", 233 | "displayName": "Academic Standing-Deans List Code" 234 | }, 235 | "fr-CA": { 236 | "group": "Registration", 237 | "displayName": "Academic Standing-Deans List Code" 238 | } 239 | } 240 | }, 241 | { 242 | "id": "6bf766aa-6c08-47e8-a1ef-12b3fe94e22f", 243 | "name": "sportActivityCode", 244 | "group": "Registration", 245 | "dataType": "string", 246 | "displayName": "Sport Activity Code", 247 | "localization": { 248 | "es": { 249 | "group": "Registration", 250 | "displayName": "Sport Activity Code" 251 | }, 252 | "en-AU": { 253 | "group": "Registration", 254 | "displayName": "Sport Activity Code" 255 | }, 256 | "en-GB": { 257 | "group": "Registration", 258 | "displayName": "Sport Activity Code" 259 | }, 260 | "en-US": { 261 | "group": "Registration", 262 | "displayName": "Sport Activity Code" 263 | }, 264 | "fr-CA": { 265 | "group": "Registration", 266 | "displayName": "Sport Activity Code" 267 | } 268 | } 269 | }, 270 | { 271 | "id": "5e42d094-a6a6-4795-a6a1-e9673fc3c5a7", 272 | "name": "sportStatus", 273 | "group": "Registration", 274 | "dataType": "string", 275 | "displayName": "Sport Status", 276 | "localization": { 277 | "es": { 278 | "group": "Registration", 279 | "displayName": "Sport Status" 280 | }, 281 | "en-AU": { 282 | "group": "Registration", 283 | "displayName": "Sport Status" 284 | }, 285 | "en-GB": { 286 | "group": "Registration", 287 | "displayName": "Sport Status" 288 | }, 289 | "en-US": { 290 | "group": "Registration", 291 | "displayName": "Sport Status" 292 | }, 293 | "fr-CA": { 294 | "group": "Registration", 295 | "displayName": "Sport Status" 296 | } 297 | } 298 | }, 299 | { 300 | "id": "e30c9c44-328f-4f8b-9ce1-604d91f9ed8d", 301 | "name": "student-academic-programs-submissions.admissionClassification.admissionCategory.code", 302 | "group": "Student", 303 | "dataType": "string", 304 | "displayName": "Academic Program Submission Admission Classification Category Code", 305 | "localization": { 306 | "es": { 307 | "group": "Student", 308 | "displayName": "Academic Program Submission Admission Classification Category Code" 309 | }, 310 | "en-AU": { 311 | "group": "Student", 312 | "displayName": "Academic Program Submission Admission Classification Category Code" 313 | }, 314 | "en-GB": { 315 | "group": "Student", 316 | "displayName": "Academic Program Submission Admission Classification Category Code" 317 | }, 318 | "en-US": { 319 | "group": "Student", 320 | "displayName": "Academic Program Submission Admission Classification Category Code" 321 | }, 322 | "fr-CA": { 323 | "group": "Student", 324 | "displayName": "Academic Program Submission Admission Classification Category Code" 325 | } 326 | } 327 | }, 328 | { 329 | "id": "9d565f3b-0339-48b4-a6a2-248a08de84c6", 330 | "name": "student-engagement-indicators.student.code", 331 | "group": "Student", 332 | "dataType": "string", 333 | "displayName": "Engagement Indicator Code", 334 | "localization": { 335 | "es": { 336 | "group": "Student", 337 | "displayName": "Engagement Indicator Code" 338 | }, 339 | "en-AU": { 340 | "group": "Student", 341 | "displayName": "Engagement Indicator Code" 342 | }, 343 | "en-GB": { 344 | "group": "Student", 345 | "displayName": "Engagement Indicator Code" 346 | }, 347 | "en-US": { 348 | "group": "Student", 349 | "displayName": "Engagement Indicator Code" 350 | }, 351 | "fr-CA": { 352 | "group": "Student", 353 | "displayName": "Engagement Indicator Code" 354 | } 355 | } 356 | }, 357 | { 358 | "id": "59e32e69-250d-47a6-9a63-2c438d3a9385", 359 | "name": "sections.descriptions.type.code", 360 | "group": "Student", 361 | "dataType": "string", 362 | "displayName": "Section Description Type Code", 363 | "localization": { 364 | "es": { 365 | "group": "Student", 366 | "displayName": "Section Description Type Code" 367 | }, 368 | "en-AU": { 369 | "group": "Student", 370 | "displayName": "Section Description Type Code" 371 | }, 372 | "en-GB": { 373 | "group": "Student", 374 | "displayName": "Section Description Type Code" 375 | }, 376 | "en-US": { 377 | "group": "Student", 378 | "displayName": "Section Description Type Code" 379 | }, 380 | "fr-CA": { 381 | "group": "Student", 382 | "displayName": "Section Description Type Code" 383 | } 384 | } 385 | }, 386 | { 387 | "id": "385c9f0a-ff0e-4c75-a065-297cd321d093", 388 | "name": "student-academic-programs.academicLevel.code", 389 | "group": "Student", 390 | "dataType": "string", 391 | "displayName": "Student Academic Program Level Code", 392 | "localization": { 393 | "es": { 394 | "group": "Student", 395 | "displayName": "Student Academic Program Level Code" 396 | }, 397 | "en-AU": { 398 | "group": "Student", 399 | "displayName": "Student Academic Program Level Code" 400 | }, 401 | "en-GB": { 402 | "group": "Student", 403 | "displayName": "Student Academic Program Level Code" 404 | }, 405 | "en-US": { 406 | "group": "Student", 407 | "displayName": "Student Academic Program Level Code" 408 | }, 409 | "fr-CA": { 410 | "group": "Student", 411 | "displayName": "Student Academic Program Level Code" 412 | } 413 | } 414 | }, 415 | { 416 | "id": "de80df83-0594-4449-b728-1a8abfec613e", 417 | "name": "student-academic-standings.standing.code", 418 | "group": "Student", 419 | "dataType": "string", 420 | "displayName": "Student Academic Standing Code", 421 | "localization": { 422 | "es": { 423 | "group": "Student", 424 | "displayName": "Student Academic Standing Code" 425 | }, 426 | "en-AU": { 427 | "group": "Student", 428 | "displayName": "Student Academic Standing Code" 429 | }, 430 | "en-GB": { 431 | "group": "Student", 432 | "displayName": "Student Academic Standing Code" 433 | }, 434 | "en-US": { 435 | "group": "Student", 436 | "displayName": "Student Academic Standing Code" 437 | }, 438 | "fr-CA": { 439 | "group": "Student", 440 | "displayName": "Student Academic Standing Code" 441 | } 442 | } 443 | }, 444 | { 445 | "id": "8d5034da-5fb6-44a3-a1ed-b28c35db0cf1", 446 | "name": "students.levelClassifications.latestClassification.code", 447 | "group": "Student", 448 | "dataType": "string", 449 | "displayName": "Student Level Classification Latest Code", 450 | "localization": { 451 | "es": { 452 | "group": "Student", 453 | "displayName": "Student Level Classification Latest Code" 454 | }, 455 | "en-AU": { 456 | "group": "Student", 457 | "displayName": "Student Level Classification Latest Code" 458 | }, 459 | "en-GB": { 460 | "group": "Student", 461 | "displayName": "Student Level Classification Latest Code" 462 | }, 463 | "en-US": { 464 | "group": "Student", 465 | "displayName": "Student Level Classification Latest Code" 466 | }, 467 | "fr-CA": { 468 | "group": "Student", 469 | "displayName": "Student Level Classification Latest Code" 470 | } 471 | } 472 | } 473 | ], 474 | "label": "FTchangeEvaluateStudentRegistrationGroup", 475 | "status": "published", 476 | "version": "12.0.0", 477 | "tenantId": "be671fc3-61cc-4df9-9d8b-c66da861b455", 478 | "createdBy": "44dfb920-3030-4097-8555-80524d8eabcd", 479 | "createdOn": "2025-08-06T14:22:09.649Z", 480 | "namespace": "data-connect", 481 | "rulesetId": "33b91fc0-c8bd-4697-8a7f-110e6dc2f8c3", 482 | "lastModifiedBy": "44dfb920-3030-4097-8555-80524d8eabcd", 483 | "lastModifiedOn": "2025-08-06T14:23:19.651Z", 484 | "versionGroupId": "1530018e-4c3b-49c5-a4e2-b7062a114716" 485 | }, 486 | "mapValues": [ 487 | { 488 | "group": "Advancement", 489 | "source": "$.rulesetData[*]", 490 | "element": "constituent-persons.names.surnames.type.code", 491 | "dataType": "string", 492 | "mapValue": "$.LastName" 493 | }, 494 | { 495 | "group": "Advancement", 496 | "source": "$.rulesetData[*]", 497 | "element": "constituent-persons.names.personalNames.type.code", 498 | "dataType": "string", 499 | "mapValue": "$.FirstName" 500 | }, 501 | { 502 | "group": "Foundation", 503 | "source": "$.rulesetData[*]", 504 | "element": "persons.veteranStatus.detail.code", 505 | "dataType": "string", 506 | "mapValue": "$.VetCode" 507 | }, 508 | { 509 | "group": "Registration", 510 | "source": "$.rulesetData[*]", 511 | "element": "academicStandingDeansListCode", 512 | "dataType": "string", 513 | "mapValue": "$.AstdCodeDL" 514 | }, 515 | { 516 | "group": "Registration", 517 | "source": "$.rulesetData[*]", 518 | "element": "sportActivityCode", 519 | "dataType": "string", 520 | "mapValue": "$.SportCode" 521 | }, 522 | { 523 | "group": "Registration", 524 | "source": "$.rulesetData[*]", 525 | "element": "sportStatus", 526 | "dataType": "string", 527 | "mapValue": "$.SportCodeDesc" 528 | }, 529 | { 530 | "group": "Student", 531 | "source": "$.rulesetData[*]", 532 | "element": "student-academic-programs-submissions.admissionClassification.admissionCategory.code", 533 | "dataType": "string", 534 | "mapValue": "$.StudentClassificationDesc" 535 | }, 536 | { 537 | "group": "Student", 538 | "source": "$.rulesetData[*]", 539 | "element": "student-engagement-indicators.student.code", 540 | "dataType": "string", 541 | "mapValue": "$.FullTimeIndicator" 542 | }, 543 | { 544 | "group": "Student", 545 | "source": "$.rulesetData[*]", 546 | "element": "sections.descriptions.type.code", 547 | "dataType": "string", 548 | "mapValue": "$.AstdCodeDLDesc" 549 | }, 550 | { 551 | "group": "Student", 552 | "source": "$.rulesetData[*]", 553 | "element": "student-academic-programs.academicLevel.code", 554 | "dataType": "string", 555 | "mapValue": "$.AstdCodeEndOfTermDesc" 556 | }, 557 | { 558 | "group": "Student", 559 | "source": "$.rulesetData[*]", 560 | "element": "student-academic-standings.standing.code", 561 | "dataType": "string", 562 | "mapValue": "$.AstdCodeEndOfTerm" 563 | }, 564 | { 565 | "group": "Student", 566 | "source": "$.rulesetData[*]", 567 | "element": "students.levelClassifications.latestClassification.code", 568 | "dataType": "string", 569 | "mapValue": "$.StudentClassificationCode" 570 | } 571 | ], 572 | "target": "rulesetData", 573 | "ignoreErrors": false 574 | } 575 | }, 576 | "For Each Student Evaluation": { 577 | "class": "forEach", 578 | "if": "message.header.isArray", 579 | "config": { 580 | "source": "payload.rulesetData.evaluations" 581 | } 582 | }, 583 | "JavaScript Transform Save input/output values": { 584 | "class": "JavaScriptTransform", 585 | "config": { 586 | "pushUndefined": true, 587 | "stopOnError": false, 588 | "draft": false, 589 | "code": "function transform (message, context) {\n // Put your code here.\nconst data=message.header.isArray?message.payload:message.payload.rulesetData;\nconst {inputs, outputs} = data;\n\n\n message.payload = {\n \n LastName: inputs[0].value || null,\n FirstName: inputs[1].value || null,\n VetCode: inputs[2].value || null,\n AstdCodeDL: inputs[3].value || null,\n SportCode: inputs[4].value || null,\n SportDesc: inputs[5].value || null,\n StudentClassificationDesc: inputs[6].value || null,\n FullTimeIndicator: inputs[7].value || null,\n AstdCodeDLDesc: inputs[8].value || null,\n AstdCodeEndOfTermDesc: inputs[9].value || null,\n AstdCodeEndOfTerm: inputs[10].value || null,\n StudentClassificationCode: inputs[11].value || null,\n StudentPriorityGroup: outputs[0]?.Value || \"No match\"\n };\nreturn message;\n}\n" 590 | } 591 | }, 592 | "Delimited Formatter": { 593 | "class": "delimitedFormatter", 594 | "config": { 595 | "normalizeColumns": false, 596 | "detail": { 597 | "columns": [ 598 | { 599 | "path": "FirstName" 600 | }, 601 | { 602 | "path": "LastName" 603 | }, 604 | { 605 | "path": "StudentClassificationCode" 606 | }, 607 | { 608 | "path": "StudentClassificationDesc" 609 | }, 610 | { 611 | "path": "FullTimeIndicator" 612 | }, 613 | { 614 | "path": "AstdCodeEndOfTerm", 615 | "label": "Academic Standing" 616 | }, 617 | { 618 | "path": "AstdCodeEndOfTermDesc", 619 | "label": "Academic Standing Desc" 620 | }, 621 | { 622 | "path": "AstdCodeDL", 623 | "label": "Dean's List" 624 | }, 625 | { 626 | "path": "AstdCodeDLDesc", 627 | "label": "Dean's List Desc" 628 | }, 629 | { 630 | "path": "SportCode" 631 | }, 632 | { 633 | "path": "SportDesc" 634 | }, 635 | { 636 | "path": "VetCode" 637 | }, 638 | { 639 | "path": "StudentPriorityGroup" 640 | } 641 | ], 642 | "columnHeaders": true 643 | } 644 | } 645 | }, 646 | "File Writer": { 647 | "class": "s3sink", 648 | "config": { 649 | "key": "StudentPriorityAssignmentReport.csv", 650 | "addNewlineAfterEachMessage": true, 651 | "includeTotalCount": false, 652 | "generatePresignedUrl": true, 653 | "writeImmediately": false, 654 | "utfEncoding": "utf-8" 655 | } 656 | }, 657 | "Ethos Consume Confirm": { 658 | "class": "ethosConsumeConfirm", 659 | "config": { 660 | "lastProcessedId": "{{message.header.BusinessEventID}}", 661 | "ignoreErrors": false 662 | } 663 | } 664 | } 665 | } --------------------------------------------------------------------------------