Primeros pasos con AngularJs: scrum cards para Redmine

En el trabajo se usa Redmine como gestor de proyectos e incidencias. Aunque existen plugins como RedmineBacklogs que permiten darle un uso más acorde a Scrum, la idea es crear algo que no necesite ningún tipo de instalación. Usaremos la API REST que proporciona Redmine para recuperar las issues y darles  el aspecto de tarjetas de Scrum para poder imprimirlas y rellenar el panel físico que todo equipo Scrum debería mantener.

Para hacerlo he escogido AngularJs, un framework JavaScript para construir aplicaciones single-page, más que nada por qué es el framework utilizado para la capa de presentación en el proyecto en el que estoy actualmente.

Equipo:

Entrada a la aplicación

El punto de entrada a nuestra aplicación será la página index.html. Es aquí donde hemos de indicar que se trata de una aplicación angularjs y cargar el javascript necesario.

<html lang="en" ng-app="app">
  <head>
    <script src="src/lib/angular.min.js"></script>

    <script src="src/app.js"></script>
    <script src="src/controllers/IssuesListController.js"></script>
  </head>

  <body>
    <div ng-view></div>
  </body>
</html>

Con la directiva ng-app habilitamos el motor de angularjs. Al indicarle el modulo app le indicamos el módulo que configurará la aplicación.

Hemos de cargar la libreria de angularjs, la del modulo que estamos cargando (app.js) y todos los controladores (IssuesListController.js)

Con la directiva ng-view indicamos el contenedor donde los controladores irán intercambiando las vistas.

Routing

Vamos a configurar la aplicación en el módulo app. Básicamente se trata de asociar una ruta a un controlador y una vista:

angular.module('app', []).

//definimos las rutas de la 'app'
config(['$routeProvider', function($routes) {
  $routes.
    when('/issues', {
      templateUrl: 'src/views/issues.html',
      controller: IssuesListController
    }).

    //cualquier ruta no definida
    otherwise({
      redirectTo: '/issues'});
    }
]);

Controlador

function IssuesListController($scope, $http) {
  $url = 'data/issues.json';
  $http({method: 'GET', url: $url}).
	success(function(data, status) {
	  $scope.status = status;
	  $scope.data = data;
	}).
	error(function(data, status) {
	  $scope.data = data || "Request failed";
	  $scope.status = status;
	});
}

A través de $scope podemos pasar/recuperar variables a la vista. El valor de $scope.data lo podremos recuperar en la vista mediante {{data}}.
Con $http podemos hacer llamadas ajax fácilmente. Es una función con un único parámetro de entrada que genera la petición HTTP y que devuelve una promesa con dos métodos específicos: success y error. De entrada vamos a hacer la petición contra un fichero estático que contiene unas cuantas issues de ejemplo. Estas issues serán pasadas a la vista mediante la variable data.

Vista

En la vista,por el momento, lo que queremos es recorrer la lista de issues y pintar cada una como una tarjeta de scrum.

<div ng-repeat="issue in data.issues">
	<div ng-include src=" 'src/views/scrumcard.html' "></div>
</div>

Para ello hacemos un bucle con ng-repeat y con ng-include incluimos el contenido de scrumcard.html donde estará la presentación final de la tarjeta de scrum. La variable data que ha llenado el controlador se ha convertido en objeto con los datos del documento json que habíamos cargado. Así, para llegar a la lista de issues, es tan sencillo como acceder a data.issues. En la vista scrumcard.html todo lo que hacemos es mostrar los datos que queremos de cada issue:

<div class="row-fluid show-grid">
	<div class="span12">
		<div class="row-fluid show-grid">
			<div class="span2">
				{{issue.tracker.name}}
			</div>
			<div class="span9">
				{{issue.author.name}}
			</div>
			<div class="span1">
				{{issue.id}}
			</div>
		</div>
		<div class="row-fluid show-grid">
			<div class="span2">
				Estimation:
			</div>
			<div class="span10">
				{{issue.subject}}
			</div>
		</div>
	</div>
</div>

Jugando con Scala: BDD + Play2 Framework

¿Qué quiero hacer?

En casa tenemos las tareas domésticas «bien» distribuidas: mi mujer se encarga de la ropa, yo de la cocina y tenemos una señora que nos echa un cable con el resto una vez por semana. Aún así, de vez en cuando cae algún reproche. El objetivo de esta aplicación será visualizar y recompensar el esfuerzo de cada uno en sacar adelante la casa. Para esto, primero definiremos las tareas que hay que hacer para que la familia tire adelante: ir a comprar, hacer la cena, poner la lavadora, bañar a Lucía, etc. Una vez definidas las tareas, el equipo -perdón, pareja- las estimará en función del esfuerzo necesario para realizarlas -¿he oído planning poker?-. Ahora el mecanismo es sencillo, sólo hay que hacer las tareas domésticas e ir acumulando puntos de esfuerzo por realizarlas. ¿Y que hago con estos puntos de esfuerzo? Ahí está lo mejor, los podrás cambiar por premios!! Juntamente con tu pareja podréis definir una serie de premios y los puntos necesarios para obtenerlos. Sed originales a la hora de pensar los premios!

¿Cómo lo quiero hacer?

El pasado noviembre finalicé el MOOC de Coursera Functional Programming Principles in Scala. El curso, que te sirve para refrescar los conceptos de programación funcional que vi de casualidad en la universidad hace casi 15 años y que tanto me gustaron, es una pequeña introducción a Scala, un lenguaje funcional que corre sobre la JVM. Una vez acabado el curso me quedé con ganas de más, con ganas de hacer alguna tontería con Scala y profundizar un poco. Buscando como empezar a hacer algo con cara y ojos, no me costó encontrar referencias a Play Framework, porqué hacer aplicaciones de cero mola, pero cuando no tienes ni idea un framework puede ayudar.

Por otro lado, hará cosa de un mes fui a un meetup organizado por Scala Barcelona Developers donde Jose Raya (@_joseraya) y Jordi Pradel (@agile_jordi) hicieron una pequeña demo de como montar una API REST con Scala aplicando BDD, en concreto cucumber. Apenas me defiendo en TDD, pero BDD es algo que, aunque nunca me he animado ha hacerlo, hace tiempo que quiero probar y creo que completaría mis prácticas en TDD.

Equipo:

No es necesario un IDE, pero puede facilitarte las cosas. Yo uso Eclipse Juno 4.2 + Scala IDE 3.0

¡Manos a la obra!

Lo primero es construir el esqueleto del nuevo proyecto. Ejecutaremos:

play new scala_housework

A continuación añadiremos las dependencias a cucumber. Editaremos el fichero project\plugins.sbt para añadir el repositorio de cucumber y el plugin

// Comment to get more information during initialization
logLevel := Level.Warn

// The Typesafe repository
resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"

// Use the Play sbt plugin for Play projects
addSbtPlugin("play" % "sbt-plugin" % "2.1.0")

// use cucumber with sbt
resolvers += "Templemore Repository" at "http://templemore.co.uk/repo"

addSbtPlugin("templemore" % "xsbt-cucumber-plugin" % "0.6.2")

Después, en project\Build.scala añadiremos la configuración de cucumber modificando el directorio donde se encontraran nuestras features

import sbt._
import Keys._
import PlayProject._
import templemore.xsbt.cucumber.CucumberPlugin

object ApplicationBuild extends Build {

    val appName         = "housework"
    val appVersion      = "1.0-SNAPSHOT"

    val buildSettings = Defaults.defaultSettings ++
                        CucumberPlugin.cucumberSettings ++
                        Seq ( CucumberPlugin.cucumberFeaturesDir := file("./test/features/"),
                              CucumberPlugin.cucumberJunitReport := true,
                              CucumberPlugin.cucumberHtmlReport := true)

    val appDependencies = Seq(
      // Add your project dependencies here,
    )

    val main = play.Project(appName, appVersion, appDependencies, settings = buildSettings).settings(
      // Add your own project settings here
    )
}

Sólo nos queda escribir el primer escenario y empezar con el desarrollo!!

Feature: HouseworksList
  In order to manage my houseworks easily, and then I could do all my houseworks and accumulate points for win a prize.
As a couple's member
I want to manage housetasks on the web pages.

  Scenario: Puts a task
    Given Set a database
    When I go to the housework list page
    Then I should see "0 task"
    When I fill in "Housework" with "wash the dishes"
    And  I push "create"
    Then I should see "1 task"
    And I am in the housework list page
    And  I should see "wash the dishes"
D:\projects\Scala\housework>play cucumber
[info] Loading project definition from D:\projects\Scala\housework\project
[info] Set current project to housework (in build file:/D:/projects/Scala/housework/)
[info] Running cucumber...
[info]
[info]
[info] You can implement missing steps with the snippets below:
[info]
[info] Given("""^Set a database$"""){ () =>
[info]   //// Express the Regexp above with the code you wish you had
[info]   throw new PendingException()
[info] }
[info] When("""^I go to the housework list page$"""){ () =>
[info]   //// Express the Regexp above with the code you wish you had
[info]   throw new PendingException()
[info] }
[info] Then("""^I should see "([^"]*)"$"""){ (arg0:String) =>
[info]   //// Express the Regexp above with the code you wish you had
[info]   throw new PendingException()
[info] }
[info] When("""^I fill in "([^"]*)" with "([^"]*)"$"""){ (arg0:String, arg1:String) =>
[info]   //// Express the Regexp above with the code you wish you had
[info]   throw new PendingException()
[info] }
[info] When("""^I push "([^"]*)"$"""){ (arg0:String) =>
[info]   //// Express the Regexp above with the code you wish you had
[info]   throw new PendingException()
[info] }
[info] Then("""^I am in the housework list page$"""){ () =>
[info]   //// Express the Regexp above with the code you wish you had
[info]   throw new PendingException()
[info] }
[success] Total time: 1 s, completed 26/03/2013 19:22:08

tip

Si os da el siguiente error:

D:\projects\Scala\housework>play cucumber
[info] Loading project definition from D:\projects\Scala\housework\project
[info] Set current project to housework (in build file:/D:/projects/Scala/housework/)
[info] Running cucumber...
[error] Exception in thread "main" java.lang.IllegalArgumentException: <strong>Not a file or directory: D:\projects\Scala\housework\prett</strong>y
[error]         at cucumber.runtime.io.FileResourceIterator$FileIterator.<init>(FileResourceIterator.java:54)
[error]         at cucumber.runtime.io.FileResourceIterator.<init>(FileResourceIterator.java:20)
[error]         at cucumber.runtime.io.FileResourceIterable.iterator(FileResourceIterable.java:19)
[error]         at cucumber.runtime.model.CucumberFeature.load(CucumberFeature.java:38)
[error]         at cucumber.runtime.RuntimeOptions.cucumberFeatures(RuntimeOptions.java:100)
[error]         at cucumber.runtime.Runtime.run(Runtime.java:92)
[error]         at cucumber.api.cli.Main.run(Main.java:20)
[error]         at cucumber.api.cli.Main.main(Main.java:12)
[success] Total time: 1 s, completed 26/03/2013 19:28:56

sólo tenéis que crear una carpeta vacía con el nombre pretty y listo! 😛


Declaración de intenciones

He de reconocerlo, soy algo desorganizado y poco constante o lo que es lo mismo, mis prioridades son muy variables. Me gusta trastear con lenguajes y frameworks que, aunque sé que no voy a poder aplicar en mi vida laboral, me llaman la atención y despiertan mis inquietudes por cambiar de trabajo. Leo blogs, how-to’s y hasta algún libro y empiezo un miniproyecto sobre el tema. Me dedico con pasión, excesiva según mi mujer, durante dos o tres semanas, el tiempo justo para tener algo funcional y haber conocido las bases del lenguaje, framework o herramienta pertinente. Pero luego, cuando toca profundizar un poco y rematar lo que he dejado embastado, lo suelo abandonar. Supongo que el no verle provecho a corto plazo y haber satisfecho mi curiosidad y verme capaz de hacer algo mínimamente funcional hace que pierda interés ante nuevos retos que, igual de poco aprovechables a corto plazo, llaman más mi atención. He decidido que me gustaría cambiar esto.

Quiero llevar todos estas webs, plugins o aplicaciones móviles que tengo a medias hasta el final, o al menos hasta que estén suficientemente maduras como para poder hacerlas accesibles al público en general. ¿Qué pretendo conseguir con esto? Principalmente, ampliar mi currículo con cosas tangibles y demostrables, que tal y cómo están las cosas nunca se sabe cuando va hacer falta tirar de él. Por otro lado, creo que es importante dar ese pasito que falta entre tener algo funcional y algo que estés orgulloso de presentar. Ese pasito que convierte el «ya se kung-fu» de Neo con el «ya se karate» de Masutatsu Ōyama.

Para conseguirlo voy a intentar ser más estricto conmigo mismo. Me he hecho una lista con las cosas con las que quiero trastear y otra de posibles proyectos donde aplicarlos. Los proyectos donde aplicarlos no han de ser una idea revolucionaria que me haga millonario. Han de responder a una necesidad mía, aunque ya haya una solución en el mercado, y servirme para el verdadero objetivo: aprender. Una vez vistas las dos listas, es cuestión de cruzarlas: elegir qué aprender, elegir dénde aplicar y montar un tablero con las historias a desarrollar. Establecer unos objetivos semanales realistas y ver la evolución. No avanzar en la siguiente historia hasta que esta no esté acabada y no coger un nuevo proyecto/tecnología hasta que este esté acabado.

Para darle visibilidad a todo esto, nace este blog. Aquí pretendo publicar un abstract de cada proyecto y hacer un seguimiento de las evoluciones de estos, explicando los problemas que me voy encontrando y publicando el código que lo soluciona. Para empezar, podéis ver en que me gustaría trastear.