I don't hide the fact that I really like Basecamp from anyone I know. Free for single projects and with a relatively low price-point, I've gotten one of my clients to use it for basic tracking of projects. I've also logged into Huddle ( a similar DotNet offering from British-based Ninian Solutions)) but haven't gotten right into it just yet.
One of the great things offered by Basecamp is the Basecamp API - which they describe as plain vanilla xml over HTTP. A lot of the samples that integrate with Basecamp are primarily web-based or widgets. I think that's primarily because of the synergy of the Web 2.0 world - but you can just as easily integrate Basecamp with Windows-based apps directly with a little bit of COM automation.
While I did some basic stuff like posting messages and updating items with the API, I found imified more useful to do updates over instant messenger instead.
However, when one of our sales managers asked for an MS Project file of all the outstanding Basecamp projects, I knew it was a challenge I wanted to face.(in FoxPro and West-Wind IP Tools - of course!)
I created a number of classes to accomplish this, using collections and custom classes.
PUBLIC loBase
loBase = CREATEOBJECT("Custom")
loBase.AddObject("Projects","Collection")
loBase.AddObject("Users","Collection")
loBase.AddObject("Companies","Collection")
But the basis for everything else was pretty straight forward:
lox = lo.gethttp()
? lox.httpconnect(basecampsite,"username","password",.t.)
lox.nhttppostmode = 4
LOCAL loXML
lcPosts = " "
lnText = 0
lnResult=lox.HTTPGetEx("/project/list",@lcPosts,@lnText,"Accept: application/xml")
loXML = CREATEOBJECT("MSXML.DomDocument")
IF loXML.LoadXML(lcPosts)
loProjects = loXML.selectnodes("//project")
FOR lni = 1 TO loProjects.length
loProj =loProjects.item(lni-1)
WITH loBase.Projects
.Add(NEWOBJECT("cbaseproject"))
.Item(.Count).ProjectName =loProj.selectsinglenode("name").nodetypedvalue
.Item(.Count).ProjectID =loProj.selectsinglenode("id").nodetypedvalue
.Item(.Count).Status =loProj.selectsinglenode("status").nodetypedvalue
ENDWITH
ENDFOR
ENDIF
I did a similar part for Companies and Users in the system. This was necessary to link projects to individuals and the like.
Within the cbaseproject class, I created a method called GetTasks , GetPeople and GetMilestones that all looked pretty much like this:
DEFINE CLASS cBaseProject AS Custom
ProjectID = 0
ProjectName = ""
Status = ""
Company = ""
PROCEDURE GetTasks
lcPosts = " "
lnText = 0
** Reconnect
lnResult=lox.HTTPGetEx("/projects/"+THIS.ProjectID+"/todos/lists",@lcPosts,@lnText,"Accept: application/xml")
loXML = CREATEOBJECT("MSXML.DomDocument")
IF loXML.LoadXML(lcPosts)
loProjects = loXML.selectnodes("//todo-list")
THIS.AddObject("TaskLists","Collection")
FOR lni = 1 TO loProjects.length
loProj =loProjects.item(lni-1)
WITH THIS.TaskLists
.Add(NEWOBJECT("cbasetask"))
.Item(.Count).TaskName =loProj.selectsinglenode("name").nodetypedvalue
.Item(.Count).Description =loProj.selectsinglenode("description").nodetypedvalue
.Item(.Count).ID =loProj.selectsinglenode("id").nodetypedvalue
ENDWITH
ENDFOR
ENDIF
ENDPROC
PROCEDURE GetMilestones
lcPosts = " "
lnText = 0
** Reconnect
lnResult=lox.HTTPGetEx("/projects/"+THIS.ProjectID+"/milestones/list",@lcPosts,@lnText,"Accept: application/xml")
loXML = CREATEOBJECT("MSXML.DomDocument")
IF loXML.LoadXML(lcPosts)
loProjects = loXML.selectnodes("//milestone")
THIS.AddObject("MileStones","Collection")
FOR lni = 1 TO loProjects.length
loProj =loProjects.item(lni-1)
WITH THIS.Milestones
.Add(NEWOBJECT("cMilestone"))
.Item(.Count).Title =loProj.selectsinglenode("title").nodetypedvalue
.Item(.Count).ID =loProj.selectsinglenode("id").nodetypedvalue
.Item(.Count).Deadline =loProj.selectsinglenode("deadline").nodetypedvalue
.Item(.Count).xml = loProj.Xml
.Item(.Count).person = getperson(loProj.selectsinglenode("responsible-party-id").nodetypedvalue)
TRY
.Item(.Count).Completed = loProj.selectsinglenode("completed").nodetypedvalue="true"
.Item(.Count).CompletedOn =loProj.selectsinglenode("completed-on").nodetypedvalue
CATCH
ENDTRY
ENDWITH
ENDFOR
ENDIF
ENDPROC
ENDDEFINE
From here, I was now simply going to create or update my MS Project file.
LOCAL lcText
loapp = GETOBJECT(,"MSProject.application")
loProject = loapp.activeproject
LOCAL lnStartRow
lnStartRow=0
lcText = ""
LOCAL loP
FOR EACH loP IN lobase.projects
IF NOT PEMSTATUS(lop,"milestones",5)
lop.GetMilestones()
ENDIF
loProject.Tasks.add(lop.projectname)
lnStartRow=lnStartRow+1
loApp.selectrange(lnStartRow,3,.f.)
loApp.outlineoutdent()
lnS = 0
FOR EACH loM IN lop.Milestones
lnS = lns+1
tsk = loProject.Tasks.add(lom.Title)
tsk.milestone = .t.
lnStartRow=lnStartRow+1
loApp.selectrange(lnStartRow,3,.f.)
IF lnS=1
loApp.outlineindent()
ENDIF
IF NOT lom.Completed
*!* ? lom.Title + " due on " + lom.deadline +" by "+lom.person
tsk.finish = DATE(VAL(LEFT(lom.deadline,4)),VAL(SUBSTR(lom.deadline,6,2)),VAL(RIGHT(lom.deadline,2)))
ELSE
tsk.percentcomplete = 100
tsk.finish = DATE(VAL(LEFT(lom.completedon,4)),VAL(SUBSTR(lom.completedon,6,2)),VAL(SUBSTR(lom.completedon,9,2)))
ENDIF
ENDFOR
ENDFOR
The end result? I can give a project file that always includes the latest updates to those who like the graphics appeal and project-reporting of MS Project.
Basecamp API
One of the great things offered by Basecamp is the Basecamp API - which they describe as plain vanilla xml over HTTP. A lot of the samples that integrate with Basecamp are primarily web-based or widgets. I think that's primarily because of the synergy of the Web 2.0 world - but you can just as easily integrate Basecamp with Windows-based apps directly with a little bit of COM automation.
While I did some basic stuff like posting messages and updating items with the API, I found imified more useful to do updates over instant messenger instead.
However, when one of our sales managers asked for an MS Project file of all the outstanding Basecamp projects, I knew it was a challenge I wanted to face.(in FoxPro and West-Wind IP Tools - of course!)
I created a number of classes to accomplish this, using collections and custom classes.
PUBLIC loBase
loBase = CREATEOBJECT("Custom")
loBase.AddObject("Projects","Collection")
loBase.AddObject("Users","Collection")
loBase.AddObject("Companies","Collection")
But the basis for everything else was pretty straight forward:
lox = lo.gethttp()
? lox.httpconnect(basecampsite,"username","password",.t.)
lox.nhttppostmode = 4
LOCAL loXML
lcPosts = " "
lnText = 0
lnResult=lox.HTTPGetEx("/project/list",@lcPosts,@lnText,"Accept: application/xml")
loXML = CREATEOBJECT("MSXML.DomDocument")
IF loXML.LoadXML(lcPosts)
loProjects = loXML.selectnodes("//project")
FOR lni = 1 TO loProjects.length
loProj =loProjects.item(lni-1)
WITH loBase.Projects
.Add(NEWOBJECT("cbaseproject"))
.Item(.Count).ProjectName =loProj.selectsinglenode("name").nodetypedvalue
.Item(.Count).ProjectID =loProj.selectsinglenode("id").nodetypedvalue
.Item(.Count).Status =loProj.selectsinglenode("status").nodetypedvalue
ENDWITH
ENDFOR
ENDIF
I did a similar part for Companies and Users in the system. This was necessary to link projects to individuals and the like.
Within the cbaseproject class, I created a method called GetTasks , GetPeople and GetMilestones that all looked pretty much like this:
DEFINE CLASS cBaseProject AS Custom
ProjectID = 0
ProjectName = ""
Status = ""
Company = ""
PROCEDURE GetTasks
lcPosts = " "
lnText = 0
** Reconnect
lnResult=lox.HTTPGetEx("/projects/"+THIS.ProjectID+"/todos/lists",@lcPosts,@lnText,"Accept: application/xml")
loXML = CREATEOBJECT("MSXML.DomDocument")
IF loXML.LoadXML(lcPosts)
loProjects = loXML.selectnodes("//todo-list")
THIS.AddObject("TaskLists","Collection")
FOR lni = 1 TO loProjects.length
loProj =loProjects.item(lni-1)
WITH THIS.TaskLists
.Add(NEWOBJECT("cbasetask"))
.Item(.Count).TaskName =loProj.selectsinglenode("name").nodetypedvalue
.Item(.Count).Description =loProj.selectsinglenode("description").nodetypedvalue
.Item(.Count).ID =loProj.selectsinglenode("id").nodetypedvalue
ENDWITH
ENDFOR
ENDIF
ENDPROC
PROCEDURE GetMilestones
lcPosts = " "
lnText = 0
** Reconnect
lnResult=lox.HTTPGetEx("/projects/"+THIS.ProjectID+"/milestones/list",@lcPosts,@lnText,"Accept: application/xml")
loXML = CREATEOBJECT("MSXML.DomDocument")
IF loXML.LoadXML(lcPosts)
loProjects = loXML.selectnodes("//milestone")
THIS.AddObject("MileStones","Collection")
FOR lni = 1 TO loProjects.length
loProj =loProjects.item(lni-1)
WITH THIS.Milestones
.Add(NEWOBJECT("cMilestone"))
.Item(.Count).Title =loProj.selectsinglenode("title").nodetypedvalue
.Item(.Count).ID =loProj.selectsinglenode("id").nodetypedvalue
.Item(.Count).Deadline =loProj.selectsinglenode("deadline").nodetypedvalue
.Item(.Count).xml = loProj.Xml
.Item(.Count).person = getperson(loProj.selectsinglenode("responsible-party-id").nodetypedvalue)
TRY
.Item(.Count).Completed = loProj.selectsinglenode("completed").nodetypedvalue="true"
.Item(.Count).CompletedOn =loProj.selectsinglenode("completed-on").nodetypedvalue
CATCH
ENDTRY
ENDWITH
ENDFOR
ENDIF
ENDPROC
ENDDEFINE
From here, I was now simply going to create or update my MS Project file.
LOCAL lcText
loapp = GETOBJECT(,"MSProject.application")
loProject = loapp.activeproject
LOCAL lnStartRow
lnStartRow=0
lcText = ""
LOCAL loP
FOR EACH loP IN lobase.projects
IF NOT PEMSTATUS(lop,"milestones",5)
lop.GetMilestones()
ENDIF
loProject.Tasks.add(lop.projectname)
lnStartRow=lnStartRow+1
loApp.selectrange(lnStartRow,3,.f.)
loApp.outlineoutdent()
lnS = 0
FOR EACH loM IN lop.Milestones
lnS = lns+1
tsk = loProject.Tasks.add(lom.Title)
tsk.milestone = .t.
lnStartRow=lnStartRow+1
loApp.selectrange(lnStartRow,3,.f.)
IF lnS=1
loApp.outlineindent()
ENDIF
IF NOT lom.Completed
*!* ? lom.Title + " due on " + lom.deadline +" by "+lom.person
tsk.finish = DATE(VAL(LEFT(lom.deadline,4)),VAL(SUBSTR(lom.deadline,6,2)),VAL(RIGHT(lom.deadline,2)))
ELSE
tsk.percentcomplete = 100
tsk.finish = DATE(VAL(LEFT(lom.completedon,4)),VAL(SUBSTR(lom.completedon,6,2)),VAL(SUBSTR(lom.completedon,9,2)))
ENDIF
ENDFOR
ENDFOR
The end result? I can give a project file that always includes the latest updates to those who like the graphics appeal and project-reporting of MS Project.
Basecamp API
Powered by ScribeFire.
Comments