Try it out
Apps
Blog
About
Contact
Developers
Docs
Login
Developers
asana@obindo.com
Written by
Chuck
Post the name of event and description to one's Asana calendar.
/************************* Documentation: API Reference: http://developer.asana.com/documentation/ API Authentication: http://developer.asana.com/documentation/#Authentication **************************/ var oauth2Credentials = {}; //#region PRIVATE set my oauth2 credentials var requestUrl = 'https://app.asana.com/-/oauth_authorize?'; var tokenUrl = 'https://app.asana.com/-/oauth_token'; var apiRoot = 'https://app.asana.com/api/1.0/'; var personalWorkspaceName = 'Personal Projects'; var regBrackets= /\[([^\]]+?)\]/g; var regName= /(^|\s)@[^\s\.\:,]+/g; var defaultTitle= ''; var g_projects= null; oauth2.getAuthRedirectUrl= function(q) { if (q) tokens.requestToken= util.getObjectFromQueryArgs(q); //create the query object to request for our "code" var args= { state: tokens.getId(), response_type: 'code', client_id: oauth2Credentials.clientID, redirect_uri: oauth2.getCallbackUrl(true), }; return requestUrl + util.getQueryArgsFromObject(args); } oauth2.onAuthResponse= function(q) { var qArgs= util.getObjectFromQueryArgs(q); //exchange the code for access token var args= { grant_type: 'authorization_code', client_id: oauth2Credentials.clientID, client_secret: oauth2Credentials.clientSecret, redirect_uri: oauth2.getCallbackUrl(true), code: qArgs.code }; var response = post({url: tokenUrl, args: args}); tokens.loadFromId(qArgs.state); tokens.accessToken= util.jsonify(response.text); if (tokens.requestToken && tokens.requestToken.redirect_uri) return tokens.requestToken.redirect_uri; } oauth2.refreshAccessToken= function() { var rt= tokens.accessToken.refresh_token; //refresh our access token if it expired var args= { grant_type: "refresh_token", client_id: oauth2Credentials.clientID, client_secret: oauth2Credentials.clientSecret, redirect_uri: oauth2.getCallbackUrl(true), refresh_token: rt }; var response= post({url: tokenUrl, args: args}); var newToken= util.jsonify(response.text); newToken.refresh_token = rt; tokens.accessToken = newToken; } //data methods function getAuthHeader() { return { "Authorization" : 'Bearer ' + tokens.accessToken.access_token }; } function getData(url) { var response = get({ url: apiRoot + url, header: getAuthHeader() }); return util.jsonify(response.text).data; } function postData(url, data) { var response= post({ url: apiRoot + url, header: getAuthHeader(), body: util.stringify({ data: data })}); return util.jsonify(response.text).data; } function getProjects() { return getData('projects?opt_fields=name,workspace.name,workspace.is_organization,team.name'); } function getWorkspaces() { return getData('workspaces?opt_fields=is_organization,name'); } function getUsers(workspaceId) { var url= (workspaceId) ? 'workspaces/'+ workspaceId +'/users' : 'users'; return getData(url); } function createProject(location) { var data= { name: location.project.name, workspace: location.workspace.id }; if (location.team) data.team = location.team.id; return postData('projects', data); } function createTask(name, notes, location, assignee, dueDate) { if (location.project && !location.project.id) { //create new project var project= createProject(location); location.project.id= project.id; } var data= { name: name, notes: notes, workspace: location.workspace.id }; if (location.project) data.projects= [ location.project.id ]; if (assignee) data.assignee= assignee.id if (dueDate) data.due_on= dueDate.startDate; return postData('tasks', data); } function addTag(workspaceId, name) { return postData('workspaces/'+ workspaceId +'/tags', { name: name }); } function addTagToTask(taskId, tagId) { return postData('tasks/'+ taskId +'/addTag', { tag: tagId }); } function getTags(workspaceId) { var arr1= getData('workspaces/'+ workspaceId +'/tags'); var arr2= []; //script engine bug with arrays? for (var i=0; i < arr1.length; i++) arr2.push(arr1[i]); return arr2; } //path methods function getWorkspaceLocations() { var spaces= getWorkspaces(); var locs= []; for (var i=0; i < spaces.length; i++) locs.push({ workspace: spaces[i] }); return locs; } function isNameMatch(itemName, searchName) { return ((itemName.toLowerCase() == searchName.toLowerCase()) || (itemName.toLowerCase() == searchName.toLowerCase() + 's')); //cheap plural test } function getBestItem(items) { //project or location var spaceItems= []; for (var i=0; i < items.length; i++) { var ws= items[i].workspace; if (ws.name == personalWorkspaceName) //ignore personal continue; if (ws.is_organization) //first org you find return items[i]; else spaceItems.push(items[i]); } return (spaceItems.length > 0) ? spaceItems[0] : items[0]; //first space if found } function getBestLocation(locations) { //of all locations, find most popular var bests= []; for (var i=0; i < locations.length; i++) { var loc= locations[i]; var max= (i == 0) ? 0 : bests[0].count; if (loc.count > max) bests= [ loc ]; else if (loc.count == max) bests.push(loc); } return getBestItem(bests); } function isPathMatch(item, pre1, pre2) { //project or location if (pre1 && pre2) return (isNameMatch(item.workspace.name, pre1) && item.team && isNameMatch(item.team.name, pre2)) else if (pre1) return (isNameMatch(item.workspace.name, pre1) || (item.team && isNameMatch(item.team.name, pre1))) else return true; } function getMatchingProjects(name, pre1, pre2) { //name and [Earlier / specs / too] var projects= []; for (var i=0; i < g_projects.length; i++) { var p= g_projects[i]; if (isNameMatch(p.name, name) && isPathMatch(p, pre1, pre2)) projects.push(p); } return projects; } function getMatchingLocations(locations, pre1, pre2) { //from [Earlier / specs / yeah] if (!pre1 && !pre2) return locations; var matches= []; for (var i=0; i < locations.length; i++) if (isPathMatch(locations[i], pre1, pre2)) matches.push(locations[i]); return matches; } function getAllLocations() { //build locations (ws, team, project count) from projects var mapAll= {}; var mapSpaces= {}; for (var i=0; i < g_projects.length; i++) { var id= g_projects[i].workspace.id; var key= getKey(g_projects[i]); if (mapAll[key]) mapAll[key].count++; else mapAll[key]= createLocation(g_projects[i], true); if (mapSpaces[id]) mapSpaces[id].count++; else mapSpaces[id]= createLocation(g_projects[i], false); } var arrAll= []; for (var key in mapAll) arrAll.push(mapAll[key]); var arrSpaces= []; for (var id in mapSpaces) arrSpaces.push(mapSpaces[id]); return { workspaces: arrSpaces, all: arrAll }; function createLocation(p, includeTeam) { var loc= { workspace: p.workspace, count: 1 }; if (includeTeam && p.team) loc.team= p.team; return loc; } function getKey(p) { var key= p.workspace.id + ''; if (p.team) key+= '|' + p.team.id; return key; } } function getLocation(search) { //no projects? do separate call for workspaces if (g_projects.length == 0) { var spaces= getWorkspaceLocations(); return getBestItem(spaces); } //special cases, find best place if (search.length == 0) { var locations= getAllLocations(); return getBestLocation(locations.workspaces); } var args= {}; if (search.length == 1) args= { name: search[0] }; else if (search.length == 2) args= { pre1: search[0], name: search[1] }; else args= { pre1: search[0], pre2: search[1], name: search[2] }; //find projects that match var projects= getMatchingProjects(args.name, args.pre1, args.pre2); if (projects.length > 0) { var best= getBestItem(projects); var loc= { workspace: best.workspace, project: { id: best.id, name: best.name } }; if (best.team) loc.team= best.team; return loc; } //no projects, build locations var locations= getAllLocations(); //just [Name] check for workspace if (!args.pre1 && !args.pre2) for (var i=0; i < locations.workspaces.length; i++) if (isNameMatch(locations.workspaces[i].workspace.name, args.name)) return { workspace: locations.workspaces[i].workspace }; //find matching pre1/pre2 to create new project var matchingLocations= getMatchingLocations(locations.all, args.pre1, args.pre2); if (matchingLocations.length > 0) { var bestLoc= getBestLocation(matchingLocations); bestLoc.project= { name: args.name }; //set name only return bestLoc; } //if no matches for [1,2,3], try to find workspace if (args.pre2) { var wsLocs= getMatchingLocations(locations.all, args.pre1); if (wsLocs.length > 0) return { workspace: wsLocs[0].workspace }; } //put in most popular workspace return getBestLocation(locations.workspaces); } //subject parsing function getSubjectTags(s) { var arr= []; if (!s) return arr; var match; while (match= regBrackets.exec(s)) { var tag= match[1].replace(/^\s+|\s+$/g,''); if (tag.length > 0) arr.push(tag); } return arr; } function getNames(s) { var arr= []; if (!s) return arr; while (match= regName.exec(s)) { var user= match[0]; user= user.substring(user.indexOf('@') + 1); user= user.replace(/^[\s\-_]+|[\s\-_]+$/g,''); if (user.length > 0) arr.push(user); } return arr; } function isUserNameMatch(userName, searchName) { if (!userName || userName.length == 0) return false; var u= userName.toLowerCase(); var s= searchName.toLowerCase(); var i= u.indexOf(' '); var first= (i < 0) ? u : u.substring(0,i); if (s.length < first.length || s.substring(0,first.length) != first) //need all of first name return false; if (s.length == first.length) //only first name was searched return true; s= s.substring(first.length).replace(/^[\s\-_]+/,''); u= u.substring(first.length).replace(/^[\s\-_]+/,''); //given last name needs to lead off last name return (u.indexOf(s) == 0); } function getAssignee(location, names) { if (names.length == 0) return getDefaultUser(); var name= names[0]; if (name.toLowerCase() == 'me') return getSelf(); var personal= (location.workspace.name == personalWorkspaceName); var users= (personal) ? getUsers() : getUsers(location.workspace.id); for (var i=0; i < users.length; i++) if (isUserNameMatch(users[i].name, name)) return users[i]; return getDefaultUser(); function getDefaultUser() { return (location.project) ? null : getSelf(); } function getSelf() { return { id: 'me', name: 'you' }; } } function getSearches(subject, sel) { var tags; if (sel) { var pName= (sel.projectName) ? util.trim(sel.projectName) : ''; tags= (pName.length > 0) ? [ pName ] : []; } else tags= getSubjectTags(subject); if (tags.length == 0) return []; var query= tags[0]; var qs= query.replace(/\\/g,'/').split('/'); //convert forward slashes var arr= []; for (var i=0; i < qs.length; i++) { var folder= qs[i].replace(/^\s+|\s+$/g,''); if (folder.length > 0) arr.push(folder); } return arr; } function getTitle(subject, dueDate) { if (!subject || subject.length == 0) return defaultTitle; var title= subject; if (dueDate && (dueDate.index + dueDate.text.length == subject.length)) { title= title.substring(0, dueDate.index); title= title.replace(/\s+(due on|due|on|for|by)\s+$/i,''); //trim date phrases } var title= title.replace(regBrackets, ' '); title= title.replace(regName, ' '); title= title.replace(/^\s+|\s+$/g,''); //trim title= title.replace(/\s{2,}/g,' '); //consecutive spaces return title; } function getText(m) { if (m.fullText && m.messages && m.messages.length > 0) return m.fullText; else if (m.strippedText) return m.strippedText; else if (m.text) return m.text; else return ''; } function getFriendlyDate(s) { // assumed 2014-07-12 var arr= s.split('-'); var dt= new Date(parseInt(arr[0], 10), parseInt(arr[1], 10) - 1, parseInt(arr[2], 10)); return dt.toDateString(); } function getConfirmMessage(location, task, title, assignee, files, dueDate) { var s= 'We created a new task in Asana for you'; s+= (assignee) ? ' -- ' : ': '; s+= (title.length > 0) ? title : '(unnamed task)'; s+= (assignee) ? ' -- and assigned it to ' + assignee.name + '.' : ''; if (dueDate) s+= '\n\nDue: ' + getFriendlyDate(dueDate.startDate); if (files.length > 0) { s+= '\n\nPlus we attached: '; for (var i=0; i < files.length; i++) s+= '\n' + files[i].name; } s+= '\n\nThe task was saved in: '; s+= location.workspace.name; if (location.team) s+= ' > ' + location.team.name; s+= ' > ' + ((location.project) ? location.project.name : 'My Tasks'); s+= '\n\nThanks for using Obindo!'; return s; } function getUploadFiles(files) { var uploads= []; if (!files) return uploads; for (var i=0; i < files.length; i++) if (files[i].contentType.indexOf('image') < 0 || !files[i].embedId) { //no embedded images files[i].name= files[i].name.replace(/;/g,','); //doesn't like semicolons uploads.push(files[i]); } return uploads; } function getDueDate(dates) { if (!dates || dates.length == 0) return null; var index= -1; var date= null; for (var i=0; i < dates.length; i++) //get last date mentioned if (dates[i].source == 'subject' && dates[i].index > index) { index= dates[i].index; date= dates[i]; } return date; } function addFiles(taskId, files) { var url= apiRoot + 'tasks/'+ taskId +'/attachments'; for (var i= 0; i < files.length; i++) post({ url: url, header: getAuthHeader(), multipartMode: 'RFC6532', args: { file: files[i] } }); } // tags function getTagNames(s) { if (!s || s.length == 0) return []; var arr= s.split(','); var names= []; for (var i=0; i < arr.length; i++) { var name= util.trim(arr[i]); if (name.length > 0 && !nameExists(name)) names.push(name); } function nameExists(name) { for (var i= 0; i < names.length; i++) if (names[i].toLowerCase() == name.toLowerCase()) return true; return false; } return names; } function addTags(taskId, workspaceId, arr) { if (arr.length == 0) return; var tags= getTags(workspaceId); for (var i=0; i < arr.length; i++) { var name= util.trim(arr[i]); var tag= getExisting(name); if (!tag) { tag= addTag(workspaceId, name); tags.push({ id: tag.id, name: tag.name }); } addTagToTask(taskId, tag.id); } function getExisting(name) { for (var i=0; i < tags.length; i++) if (tags[i].name.toLowerCase() == name.toLowerCase()) return tags[i]; return null; } } //on load $(function() { g_projects= getProjects(); var names; var sel= message.selections; if (sel) { var name= (sel.assignee) ? util.trim(sel.assignee) : ''; names= (name.length > 0) ? [name] : []; } else names= getNames(message.subject); var searches= getSearches(message.subject, sel); var dates= (sel && sel.dates) ? sel.dates : message.dates; var location= getLocation(searches); var assignee= getAssignee(location, names); var dueDate= getDueDate(dates); var title; if (sel) { title= (sel.name) ? util.trim(sel.name) : ''; if (title.length == 0) title= defaultTitle; } else title= getTitle(message.subject, dueDate); var text= (sel && sel.text) ? sel.text : getText(message); var task= createTask(title, text, location, assignee, dueDate); if (sel && sel.tags && location.workspace.name != personalWorkspaceName) { //no personal tags var tagNames= getTagNames(sel.tags); addTags(task.id, location.workspace.id, tagNames); } var files= getUploadFiles(message.files); if (files.length > 0) addFiles(task.id, files); message.appUrl= (location.project) ? 'https://app.asana.com/0/'+ location.project.id +'/' + task.id : 'https://app.asana.com/0/'+ location.workspace.id +'/' + task.id; text= getConfirmMessage(location, task, title, assignee, files, dueDate); sendEmail({ to: message.from.emailAddress, subject: 'Obindo created an Asana task for you!', text: text, templateFields: { recipientName: message.from.name, buttonText: 'View in Asana', buttonLink: message.appUrl } } ); });