Simple Project Management

I’ve been thinking and working on a simple idea, that probably will sound bad for some old school project managers. I’ve been living, working, and teaching about the PMI standard for almost 3 years, and I’ve been working on agile software development since forever – because in software development, scope is a moving target so you cannot count on your “printed gantt chart”.

The Idea

To build a simple, extremely simple an online tool (webapp) to handle projects, but no only software projects -which is, I believe, the common approach. Build a set of easy-to-use tools to breakdown work (using drag-and-drop automagic for WBS or even to create Product Backlogs as you will probably build the WBS graphically. I’m not sure about how it will result, but I’m sure you get the point behind the idea. My mind is thinking about a built-in worksheet (like in a spreadsheets tool) to dynamically generate reports that works also as input forms – so you don’t have the Import/Export pain (if you use any online tool every day you know what I’m talking about).

I love little colored pieces of paper, boards (aka information radiators), eDocs with names and tasks and colors (I do love eDocs) – I’m not sure why Google does not have a Project Tool in its office suite.

I’m not sure when, while performing as PM, Professor, Speaker, and VP of Special Projects for PMI Colombia Chapter, I will get the time to work on this personal project.

 


Labs & RRapido Methodology

http://www.flickr.com/photos/margolove/1810357551/sizes/s/Eight+ months ago I decided to bring a change to my life. I decided to resign to my Account Director position at Studiocom. I was sure that even when Studiocom was named as one of the best place to work here in Colombia, there was no opportunity for me to grow personal or professional. I was working hard more than a year on amazing projects, getting results for the company, and the client, but with no professional opportunities, or any recognition or reward, feeling frustration and of course, leading me to a no-return situation.

Then, I took a chance with a well-known entrepreneur at Torrenegra Labs – thanks to Leo Suarez and Alex Torrenegra. It was not a smooth transition. At the beginning I was supposed to work on a project (shh! It is a secret one), however after our first formal review on the scope and budget, we decided to keep this initiative on hold. It was back on December! OMG! What am I going to do?

Fortunately for me, Alex invited me to join the LetMeGo Team as the owner of the Referral Program (during my first three months here at Torrenegra Labs I offered my help on LetMeGo project giving a hand to launch the Beta on time). I joined the team last January. So, “What? Referral what?” I was confused and I was feeling ignorant, what is a referral program? and how am I suppose build a successful referral program?

I started working as Architect of the LetMeGo Subsystem on charge of the Referral Program, and then of course, as any good Architect, I started coding, but not coding like I was used to. That is when RRapido Methodology and I formally met. Even when it is a mix of Agile, Traditional Development and Test Driven development, I have to say that the most valuable asset that we have is our methodology. I’m not saying it is perfect or a “One-Size-Fits-All”, however I will give my impressions on it:

  • Applied Test Driven Development reduces the chances of getting errors on late development phases or iterations (I will not discuss here if they are the same or not).
  • Extremely well document UC, TC and UI – LMG is the first project I had participated on were tech docs are almost perfect and detailed good enough to allow clear trace between code, tests and UC – it is a little bit confusing because Agile Manifesto includes a line “Working software over comprehensive documentation” – what does it means anyway? Even on traditional approach you are always looking for “working software”.
  • Noisy communications – RRapido fails on how things are communicated. We had what we call here in Colombia “meetin-gitis”, but through Skype. Most of the times we over communicate everything to everybogy. With time you get used to it, but it is like be in a party with everybody talking each other and with loud music.
  • Microblogging looks like useless. We use Yammer, I microblogging tool for private(?) teams. We used it to promote our tasks every day -I’ve to admit that I’m not most dedicated team member to that.
  • Small group with one “I-know-everything-about-the-system” manager. RRapido, and the most significant and valuable addition to our team is what Scrum calls the Product Owner. Alex is our product owner, he is a technical person that has developed a great “business” sense. With his experience and dedication RRapido and the whole project is kept on track. There is no doubt about it. One day without Alex is a day when things slow down (a little bit)

So it is time to talk about Mr. Alex or  Don Alex. I disagree about few practices on Alex management style however, and based on the facts and the results I say: He is on care of EVERY detail of the system, leading the team to a successful product and “working software”. He is the dream of every development team, a dedicated and objective Product Owner that understands technical challenges and risks of every decision. Alex does not ask for outstanding deliverables all the time, but when he does that, he also provides the tools and time to succeed.

RRapido Methodology depends 100% on the Product Owner -what we call Product Developer. He/she is the key of the RRapido Methodology, as the keeper of the process. It is so difficult to know about the system, the business, the market, the customer drivers, to have relevant technical background on the development, to have passion about the business, and also to be the process owner, that RRapido becomes a process for few -maybe a process that can be adapted for those who had worked with Alex.

Recomendations

As my dad always says, if your are complaining about something that you can change, why you do not change it. I decided then to write this post, sure that Torrenegra Team will read it and probably -if I succeed- will include few process to the methodology.

  • Methodology is a set of steps, it is not a ToDo list. RRapido feels like a set of things to do, but I would prefer if we can set them as a procedure.
  • Communications are noisy, One-on-One meetings are great, Publishing Notification Board is ok, all other are noisy. I would prefer a message board or workspace site.
  • Microblogging is useless when weekly tasks have been set. Remove it or use it to assign tasks, not to report them.
  • Documentation. I’m in love with documentation, it is not perfect, but it supplies all developers need to code, test and validate the required functionality, however, navigation between UC, UT and UI is painfull. I would look for another way to link UC and UI (maybe a two column page or something similar).
  • By formalizing RRapido steps, dependency on the Product Developer role will be reduced.
  • Test Driven Development Rocks!

Import contacts using Google’s API

Maybe the most important thing about this post is how to use HTTP based APIs in a simple way -using a wrapper for cURL PHP extension. Most of the examples available on the Web are kind of out-of-date, based on the idea that there is no a better abstraction layer.

Requirements / Example environment configuration.

This example was originally built for CakePHP, but as you can see, it can be used or migrated to several frameworks – the key is to use the PEAR component. Note: I believe that this example will make most of the example out there crappy – and I hope it, so other developers could use it.

 
// PEAR Package HTTP_Request2
// http://pear.php.net/manual/en/package.http.http-request2.php
require_once('HTTP' . DS . 'Request2.php');
 
class GoogleImportComponent extends Object {
 
	private $username = null;
	private $password = null;
	private $token = null;
 
	/**
	 * Authenticate user credential against provider services
	 *
	 * @param string $username User's login
	 * @param string $password User's account password
	 * @see http://code.google.com/apis/contacts/docs/3.0/developers_guide_protocol.html
	 */
	function authenticate($username, $password) {
		$this->username = $username;
		$this->password = $password;
 
		$hrc = new HTTP_Request2('https://www.google.com/accounts/ClientLogin', HTTP_Request2::METHOD_POST);
		$hrc->setAdapter('HTTP_Request2_Adapter_Curl');
		$hrc->setConfig('ssl_verify_peer', false);
		$hrc->addPostParameter('accountType', 'HOSTED_OR_GOOGLE');
		$hrc->addPostParameter('service', 'cp');
		$hrc->addPostParameter('source', 'MY_APP_ID');
		$hrc->addPostParameter('Email', $this->username);
		$hrc->addPostParameter('Passwd', $this->password);
 
		$response = $hrc->send();
		$body = $response->getBody();
		if ($response->getStatus() == 200) {
			$this->token = substr($body, strpos($body, 'Auth=') + 5);
		}
		else {
			throw new Exception($response->getReasonPhrase(), $response->getStatus());
		}
	}
 
	/**
	 * Return the contacts' email list
	 *
	 * @return array
	 * @see http://code.google.com/apis/contacts/docs/3.0/developers_guide_protocol.html
	 */
	function get() {
		$addresses = array();
 
		$hrc = new HTTP_Request2("http://www.google.com/m8/feeds/contacts/{$this->username}/full", HTTP_Request2::METHOD_GET);
		$hrc->setHeader('Authorization', "GoogleLogin auth={$this->token}");
		$response = $hrc->send();
		if ($response->getStatus() == 200) {
			$xml = simplexml_load_string(utf8_encode($response->getBody()));
			$xmladd =  $xml->xpath("//gd:email/@address");
			foreach ($xmladd as $value) {
				$data = get_object_vars($value);
				$addrs = $data['@attributes']['address'];
				if ($addrs != $this->username) {
					$addresses[] = $addrs;
				}
			}
		}
		else {
			throw new Exception($response->getReasonPhrase(), $response->getStatus());
		}
 
		return $addresses;
	}
}

That’s it. It is not a huge amount of code with HTTP protocol related instructions – like the ones you can get for free. It relies on HTTP_Request2 PEAR module, that even if it’s on beta, works like a charm.


Search Portlet

Few months ago I was developing a project on top of JBoss Portal. Unfortunately, JBoss Portal 2.7.x is a nice Portlet Container but a crappy CMS/Search engine -even when it is supported on Apache’s JackRabbit. However, I was forced to develop a search portlet. It wasn’t easy, and you will probably find that it is not a perfect solution. However I really hope you find this Search Portlet really useful.

SearchPortlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package portal.portlets;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
 
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletSecurityException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.UnavailableException;
import javax.portlet.WindowState;
 
import org.apache.log4j.Logger;
import org.jboss.portal.cms.CMS;
import org.jboss.portal.cms.CMSException;
import org.jboss.portal.cms.Command;
import org.jboss.portal.cms.model.File;
import org.jboss.portal.core.cms.ui.CMSPortlet;
import org.jboss.portal.search.FederatedQuery;
import org.jboss.portal.search.QueryConversionException;
import org.jboss.portal.search.federation.SearchFederation;
import org.jboss.portal.search.impl.jcr.JCRQuery;
import org.jboss.portal.search.impl.jcr.JCRQueryConverter;
 
public class SearchPortlet extends CMSPortlet {
 
  private static Logger log = Logger.getLogger(SearchPortlet.class);
  private SearchFederation SearchService;
  private CMS CMSService;
 
  public void init(PortletConfig config) throws PortletException {
    super.init(config);
  }
 
  public void destroy() {
    super.destroy();
  }
 
  /*
   * (non-Javadoc)
   *
   * @see javax.portlet.Portlet#init()
   */
  @Override
  public void init() throws PortletException {
    this.SearchService = (SearchFederation) getPortletContext().getAttribute("SearchFederationService");
    this.CMSService = (CMS) getPortletContext().getAttribute("CMS");
 
    if (this.SearchService == null) {
      throw new PortletException("Unable to start search service");
    }
 
    if (this.CMSService == null) {
      throw new PortletException("CMS services not available");
    }
 
    super.init();
  }
 
  /*
   * (non-Javadoc)
   *
   * @see javax.portlet.GenericPortlet#doView(javax.portlet.RenderRequest,
   * javax.portlet.RenderResponse)
   */
  @Override
  protected void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException, UnavailableException {
 
    String portalInstance = request.getWindowID().substring(0, request.getWindowID().indexOf('/', 1));
    PortletRequestDispatcher prd = null;
    String operation = (String) request.getParameter("op");
 
    if (operation != null && operation.equals("search")) {
      String queryString = (String) request.getParameter("query");
      if (queryString != null && queryString.length() > 0) {
        request.setAttribute("files", this.getFiles(queryString, portalInstance));
        prd = this.getPortletContext().getRequestDispatcher("/WEB-INF/jsp/results.jsp");
      } else {
        prd = getPortletContext().getRequestDispatcher("/WEB-INF/jsp/search.jsp");
      }
      prd.include(request, response);
    } else if (operation != null && operation.equals("display")) {
      super.doView(request, response);
    } else {
      prd = getPortletContext().getRequestDispatcher("/WEB-INF/jsp/search.jsp");
      prd.include(request, response);
    }
  }
 
  /*
   * (non-Javadoc)
   *
   * @see javax.portlet.Portlet#processAction(javax.portlet.ActionRequest,
   * javax.portlet.ActionResponse)
   */
  @Override
  public void processAction(ActionRequest request, ActionResponse response) throws PortletException, PortletSecurityException, IOException {
    String profile;
 
    String operation = (String) request.getParameter("op");
    if (operation != null && operation.length() > 0) {
      if (operation.equals("ask")) {
        response.setWindowState(WindowState.NORMAL);
      } else {
        response.setWindowState(WindowState.MAXIMIZED);
      }
      response.setRenderParameter("op", operation);
    } else {
      response.setRenderParameter("op", "ask");
      response.setWindowState(WindowState.NORMAL);
    }
 
    if (operation.equals("search")) {
      String queryString = (String) request.getParameter("query");
      if (queryString != null && queryString.length() > 0) {
        response.setRenderParameter("query", queryString);
      }
    }
  }
 
  private List getFiles(String queryString, String portal) {
    List files = null;
    List filteredFiles = new ArrayList();
 
    if (queryString != null && queryString.length() > 0) {
      FederatedQuery query = new FederatedQuery(queryString);
      JCRQueryConverter converter = new JCRQueryConverter();
 
      try {
        Command searchCommand = CMSService.getCommandFactory().createSearchCommand((JCRQuery) converter.convert(query));
        files = (List) CMSService.execute(searchCommand);
 
        // You can filter files if needed by reviewing its path under the CMS
        // structure, by name, or any other criteria that fits your needs.
 
      } catch (CMSException e) {
        log.error("CMS Error", e);
      } catch (QueryConversionException e) {
        log.warn("Conversion Error", e);
      }
    }
 
    return files;
  }
}

I used two JSP pages to support this portlet.

  • search.jsp – Search form – the standard portlet view
  • results.jsp – Results list page – appears with the portlet MAXIMIZED and displays CMS files that matches the criteria

search.jsp – (WP-Syntax does not support JSP)

1
2
3
4
5
6
7
8
<h2>Search</h2>
<form action="<%=contactActionURL%>" method="POST">
  <fieldset style="border: none;">
    <input name="op" type="hidden" value="search" />
    <input name="query" type="text" />
    <input type="submit" value="Search" />
  </fieldset>
</form>

results.jsp – (WP-Syntax does not support JSP)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ page import="org.jboss.portal.cms.model.File"%>
<%@ page import="java.util.List"%>
<%@ page language="java" extends="org.jboss.portal.core.servlet.jsp.PortalJsp"%>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
<%
  List<File> files = (List<File>) request.getAttribute("files");
  if (files != null && files.size() > 0) { %>
<h1>RESULTADOS DE B&Uacute;QUEDA</h1>
  <%
    for (int i = 0; i < files.size(); i++) {
        File file = files.get(i);
  %>
  <h2><%= file.getName() %></h2>
  <p><% if (file.getDescription() != null && file.getDescription().length() > 0) { %><%= file.getDescription() %><% } %><a href="<portlet:actionURL><portlet:param name="op" value="display" /><portlet:param name="file" value="<%= file.getBasePath() %>" /></portlet:actionURL>">Ver m&aacute;s...</a></p>
  <hr />
  <%
    }
  %>
<% } else { %>
<h1>NO HAY RESULTADOS</h1>
<% } %>
<p align="center"><a href="<portlet:actionURL><portlet:param name="op" value="ask" /></portlet:actionURL>">Volver</a></p>

Include the following lines on your jboss-portlet.xml file.

1
2
3
4
5
6
7
8
9
10
11
<service>
  <service-name>CMS</service-name>
  <service-class>org.jboss.portal.cms.CMS</service-class>
  <service-ref>:service=CMS</service-ref>
</service>
 
<service>
  <service-name>SearchFederationService</service-name>
  <service-class>org.jboss.portal.search.federation.SearchFederation</service-class>
  <service-ref>:service=SearchFederationService</service-ref>
</service>

And finally add the following lines on your portlet.xml file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<portlet>
  <description>DESC</description>
  <portlet-name>SearchPortlet</portlet-name>
  <display-name>NAME</display-name>
  <portlet-class>portal.portlets.SearchPortlet</portlet-class>
  <supports>
    <mime-type>text/html</mime-type>
    <portlet-mode>VIEW</portlet-mode>
  </supports>
  <resource-bundle>Resource</resource-bundle>
  <portlet-info>
    <title>SEARCH</title>
  </portlet-info>
  <portlet-preferences>
    <preference>
      <name>indexpage</name>
      <value>/default/index.html</value>
      </preference>
    <preference>
      <name>setBrowserTitle</name>
      <value>false</value>
    </preference>
  </portlet-preferences>
</portlet>
 
<filter>
  <filter-name>JBoss Portlet Filter</filter-name>
  <filter-class>org.jboss.portlet.filter.JBossPortletFilter</filter-class>
  <lifecycle>ACTION_PHASE</lifecycle>
  <lifecycle>RENDER_PHASE</lifecycle>
</filter>
 
<filter-mapping>
  <filter-name>JBoss Portlet Filter</filter-name>
  <portlet-name>SearchPortlet</portlet-name>
</filter-mapping>

Team Building vs. Labor Climate

Few days ago I had a conversation with one of the most active Bloggers I had ever known: Bas de Baar about project management in Colombia.I’m not the most experienced project manager here in Colombia, but probably I had worked for few companies that provide me with a very well understanding of what is going on with project management in Colombia.

Project Management isn’t new in Colombia. At least for IT and software related companies Project Managers have been there forever (due to the nature of the IT and software development processes). But, there is something missing about PM here in Colombia: Team Building.

I do believe that Labor Climate and Team Building aren’t the same. As I said to Bas, almost all Colombians -good, not so good, and bad ones- will try to be your friend. Colombian people is really appreciated because of their willingness and attitude of service and camaraderie (did I write it right?). However those are two different things: try to be your friend, and be a good boss, project manager or director.

And that’s why Labor Climate seems to be amazing on Colombian SW development related companies even when “team building” is not a priority. And it could be good, because of its impact on the organizational behavior, improve the labor climate will probably  boost the team efficiency and collaboration. But, not to perform team building activities will be bad for long-term employee & employers relationship, because “personal” friendship isn’t necessarily based on the idea of personal and professional growth and career development.

Furthermore,  SW developers, designers, and architects, and IT project managers, prefers to have project-based relationships with their employers. And for those who know, contractors, even if they are good doing what they do, increase the probability of certain risks to occur -example: dependency on people because there is no knowledge spread opportunity.

To conclude. there is a big difference between “having a great time with amazing people at the office” and “having a great work experience”. It should be a balance between labor climate and team building to ensure a short-term motivation, but also a long-term relationship between employers and employees.