OmniPlan: Conference Example (Fetch Remote Data)
In the following version of the Conference Example plug-in, the JSON data used to construct the conference outline is retrieved from a password-protected file stored on this website.
The plug-in code incorporates four instances of await statements within an asynchronous wrapper rather than relying on explicit Promise handlers and code blocks.
The plug-in performs the following steps:
- Prompts the user to input the conference title, conference duration, and start date (Monday). For demonstration purposes, the plug-in provides default values for each.
- Constructs the conference tasks on the project timeline.
- Prompts the user to input the URL of the password protected JSON data file, and the account name (otto-automator) and password (omni-automation) for the file.
- Retrieves the JSON data containing information regarding the session IDs, titles, presenters, and presenter email addresses, and populates the conference outline with the data, importing each of the presenters as a staff resource that is assigned to related tasks (conference sessions).
- Prompts the user to save the project to disk.
Conference Example (Fetch Remote Data)
/*{
"type": "action",
"targets": ["omniplan"],
"author": "Otto Automator",
"identifier": "com.omni-automation.op.fetch-build-conference-example",
"version": "2.5",
"description": "Adapts the current project to display a basic 1-track conference. Conference data is downloaded from a password-protected JSON file. The password is: omni-automation",
"label": "Fetch and Build Single-Track Conference",
"shortLabel": "Conference",
"paletteLabel": "Conference",
"image": "calendar"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
try {
// INFORMATIONAL ALERT
let openAlertTitle = "Conference Example"
let openAlertMessage = "This plug-in will create a scenario for a 3-day single-track conference using data downloaded from a password-protected JSON file.\n\nThe password is: omni-automation"
let openAlertResult = await new Alert(openAlertTitle, openAlertMessage).show()
// CREATE FORM FOR GATHERING USER INPUT
let inputForm = new Form()
// CREATE TEXT FIELD AND DATE FIELD OBJECTS
let conferenceTitleField = new Form.Field.String(
"conferenceTitle",
"Conference Title",
null
)
let conferenceLengthMenu = new Form.Field.Option(
"conferenceLength",
"Length (in days)",
[1,2,3],
["1", "2", "3"],
3
)
// GET 2ND MONDAY OF NEXT MONTH
let cal = Calendar.current
let today = cal.startOfDay(new Date())
let dc = cal.dateComponentsFromDate(today)
dc.day = 1
dc.month = dc.month + 1
let monthLength = new Date(dc.year, dc.month, 0).getDate()
let matchedDays = new Array()
let weekdayIndex = 1 // Monday
for (var i = 1; i < (monthLength + 1); i++) {
dc.day = i
var d = cal.dateFromDateComponents(dc)
if (d.getDay() === weekdayIndex){matchedDays.push(d)}
}
let targetMonday = matchedDays[1]
let startDateField = new Form.Field.Date(
"startDate",
"Start Date (Monday)",
targetMonday
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(conferenceTitleField)
inputForm.addField(conferenceLengthMenu)
inputForm.addField(startDateField)
// VALIDATE THE USER INPUT. DAY MUST BE A MONDAY, AND TITLE MUST BE PROVIDED.
inputForm.validate = function(formObject){
let textValue = formObject.values["conferenceTitle"]
let textStatus = (textValue && textValue.length > 0) ? true:false
let startDateObject = formObject.values["startDate"]
let startDateStatus = (startDateObject && startDateObject.getDay() === 1) ? true:false
let validation = (textStatus && startDateStatus) ? true:false
return validation
}
// PRESENT THE FORM TO THE USER
let formPrompt = "Provide the conference title, length in days, and the conference start date (Monday):"
let formObject = await inputForm.show(formPrompt,"Continue")
// TITLE
let conferenceTitle = formObject.values['conferenceTitle']
document.project.title = conferenceTitle
// START DATE. SET TIME OF THE CHOSEN DATE TO MIDNIGHT, AND APPLY DATE AS THE PROJECT’S START DATE.
let startDate = formObject.values['startDate']
startDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate(), 0, 0, 0)
actual.startDate = startDate
// CONFERENCE LENGTH IN DAYS
let conferenceLength = formObject.values['conferenceLength']
conferenceLength = parseInt(conferenceLength)
// CLEAR ALL TASKS AND RESOURCES
actual.rootTask.descendents().forEach((task)=>{task.remove()})
actual.rootResource.descendents().forEach((rsc)=>{rsc.remove()})
// SESSION PARAMETERS
let sessionTimes = ["8:30","9:45","11:00","13:30","14:45","16:00"]
let sessionDuration = Duration.elapsedHourMinSec(1, 0, 0)
// CREATE THE SESSIONS (TASKS) USING THE DAY/SESSION INDEXES TO DERIVE SESSION IDS (101, 203, 305, ETC.)
let dayIndex
for (dayIndex = 1; dayIndex < conferenceLength + 1; dayIndex++) {
let aDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + (dayIndex- 1) , 0, 0, 0)
sessionTimes.forEach((time, sessionIndex) => {
let hoursMinutes = time.split(":")
let hours = Number(hoursMinutes[0])
let minutes = Number(hoursMinutes[1])
let taskDateTime = new Date(aDate.getFullYear(), aDate.getMonth(), aDate.getDate(), hours, minutes, 0)
let task = actual.rootTask.addSubtask()
// check for 4.x property, otherwise use 3.x property
if (task.manualStartDate !== undefined){
task.manualStartDate = taskDateTime
} else {
task.lockedStartDate = taskDateTime
}
task.duration = sessionDuration
task.title = String(dayIndex) + "0" + String(sessionIndex)
})
}
// CREATE FORM FOR RETRIEVING SECURE REMOTE DATA
let fetchForm = new Form()
// CREATE TEXT FIELDS
let urlField = new Form.Field.String(
"targetURL",
"URL",
"https://omni-automation.com/secure/conference-sessions.json"
)
let nameField = new Form.Field.String(
"accountName",
"Name",
"otto-automator"
)
let passwordField = new Form.Field.Password(
"accountPassword",
"Password",
null
)
// ADD THE FIELDS TO THE FORM
fetchForm.addField(urlField)
fetchForm.addField(nameField)
fetchForm.addField(passwordField)
// VALIDATE THE USER INPUT
fetchForm.validate = function(formObject){
let targetURL = formObject.values["targetURL"]
let schema = ["http://","https://"]
let urlStatus = false
if (targetURL){
let i;
for (i = 0; i < schema.length; i++) {
if (targetURL.startsWith(schema[i], 0) && URL.fromString(targetURL)){urlStatus = true}
}
}
let providedName = formObject.values["accountName"]
let nameStatus = (providedName && providedName.length > 0) ? true:false
let providedPassword = formObject.values["accountPassword"]
let passwordStatus = (providedPassword && providedPassword.length > 0) ? true:false
let validation = (urlStatus && nameStatus && passwordStatus) ? true:false
return validation
}
// PRESENT THE FORM TO THE USER
let fetchFormPrompt = "Retrieve Secure Conference Data"
let fetchFormObject = await fetchForm.show(fetchFormPrompt, "Continue")
// CONSTRUCT ACCOUNT/PASSWORD CREDENTIALS
let accountName = fetchFormObject.values["accountName"]
let accountPassword = fetchFormObject.values["accountPassword"]
let data = Data.fromString(accountName + ":" + accountPassword)
let credentials = data.toBase64()
// CONSTRUCT HTTP REQUEST
let targetURL = fetchFormObject.values["targetURL"]
let request = URL.FetchRequest.fromString(targetURL)
request.method = 'GET'
request.cache = "no-cache"
request.headers = {"Authorization": "Basic" + " " + credentials}
// EXECUTE REQUEST
let response = await request.fetch()
let responseCode = response.statusCode
if (responseCode >= 200 && responseCode < 300){
let json = JSON.parse(response.bodyString)
json.forEach(sessionData => {
// EXTRACT DATA FOR EACH SESSION
let speakerName = sessionData["Speaker"]
let speakerEmail = sessionData["Email"]
let sessionID = sessionData["ID"]
let sessionTitle = sessionData["Title"]
// PROCESS EACH EXISTING TASK
let tasks = actual.rootTask.subtasks
tasks.forEach(task => {
if (task.title === sessionID){
task.title = sessionID + ": " + sessionTitle
let rsc = actual.resourceNamed(speakerName)
if(!rsc){
rsc = actual.rootResource.addMember()
rsc.type = ResourceType.staff
rsc.name = speakerName
rsc.email = speakerEmail
rsc.costPerUse = Decimal.fromString("500.00")
}
task.addAssignment(rsc)
}
})
})
// SAVE THE DOCUMENT
document.project.title = conferenceTitle
document.save()
} else {
new Alert(String(responseCode), "An HTTP Request error occurred.").show()
console.error(JSON.stringify(response.headers))
}
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
});
action.validate = function(selection, sender){
return true
};
return action;
})();