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 ALERTlet 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 INPUTlet inputForm = new Form()// CREATE TEXT FIELD AND DATE FIELD OBJECTSlet 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 MONTHlet cal = Calendar.currentlet today = cal.startOfDay(new Date())let dc = cal.dateComponentsFromDate(today)dc.day = 1dc.month = dc.month + 1let monthLength = new Date(dc.year, dc.month, 0).getDate()let matchedDays = new Array()let weekdayIndex = 1 // Mondayfor (var i = 1; i < (monthLength + 1); i++) {dc.day = ivar 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 FORMinputForm.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:falselet startDateObject = formObject.values["startDate"]let startDateStatus = (startDateObject && startDateObject.getDay() === 1) ? true:falselet validation = (textStatus && startDateStatus) ? true:falsereturn validation}// PRESENT THE FORM TO THE USERlet formPrompt = "Provide the conference title, length in days, and the conference start date (Monday):"let formObject = await inputForm.show(formPrompt,"Continue")// TITLElet 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 DAYSlet conferenceLength = formObject.values['conferenceLength']conferenceLength = parseInt(conferenceLength)// CLEAR ALL TASKS AND RESOURCESactual.rootTask.descendents().forEach((task)=>{task.remove()})actual.rootResource.descendents().forEach((rsc)=>{rsc.remove()})// SESSION PARAMETERSlet 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 dayIndexfor (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 propertyif (task.manualStartDate !== undefined){task.manualStartDate = taskDateTime} else {task.lockedStartDate = taskDateTime}task.duration = sessionDurationtask.title = String(dayIndex) + "0" + String(sessionIndex)})}// CREATE FORM FOR RETRIEVING SECURE REMOTE DATAlet fetchForm = new Form()// CREATE TEXT FIELDSlet 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 FORMfetchForm.addField(urlField)fetchForm.addField(nameField)fetchForm.addField(passwordField)// VALIDATE THE USER INPUTfetchForm.validate = function(formObject){let targetURL = formObject.values["targetURL"]let schema = ["http://","https://"]let urlStatus = falseif (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:falselet providedPassword = formObject.values["accountPassword"]let passwordStatus = (providedPassword && providedPassword.length > 0) ? true:falselet validation = (urlStatus && nameStatus && passwordStatus) ? true:falsereturn validation}// PRESENT THE FORM TO THE USERlet fetchFormPrompt = "Retrieve Secure Conference Data"let fetchFormObject = await fetchForm.show(fetchFormPrompt, "Continue")// CONSTRUCT ACCOUNT/PASSWORD CREDENTIALSlet accountName = fetchFormObject.values["accountName"]let accountPassword = fetchFormObject.values["accountPassword"]let data = Data.fromString(accountName + ":" + accountPassword)let credentials = data.toBase64()// CONSTRUCT HTTP REQUESTlet targetURL = fetchFormObject.values["targetURL"]let request = URL.FetchRequest.fromString(targetURL)request.method = 'GET'request.cache = "no-cache"request.headers = {"Authorization": "Basic" + " " + credentials}// EXECUTE REQUESTlet response = await request.fetch()let responseCode = response.statusCodeif (responseCode >= 200 && responseCode < 300){let json = JSON.parse(response.bodyString)json.forEach(sessionData => {// EXTRACT DATA FOR EACH SESSIONlet speakerName = sessionData["Speaker"]let speakerEmail = sessionData["Email"]let sessionID = sessionData["ID"]let sessionTitle = sessionData["Title"]// PROCESS EACH EXISTING TASKlet tasks = actual.rootTask.subtaskstasks.forEach(task => {if (task.title === sessionID){task.title = sessionID + ": " + sessionTitlelet rsc = actual.resourceNamed(speakerName)if(!rsc){rsc = actual.rootResource.addMember()rsc.type = ResourceType.staffrsc.name = speakerNamersc.email = speakerEmailrsc.costPerUse = Decimal.fromString("500.00")}task.addAssignment(rsc)}})})// SAVE THE DOCUMENTdocument.project.title = conferenceTitledocument.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;})();