StickyPrint – Behind the Scenes

In this part, I will walk you through the ideas and technologies involved in creation of StickyPrint – Our Daily Standup Facilitator. If you have not read the part-1 yet, I strongly recommend you to read it first before going through this article.

There are two puzzles to solve to make it work. First we need to get sprint data from Rally and transform it to a format we want to see in our task board. Once we have the data, we need to find out a way to reliably print it in Sticky Notes. We were confident that we can solve Puzzle-1 (data retrieval and transformation) very easily in an hour or so, if we programmers can’t do it who else can. But how do we print something in Sticky Notes? No matter whatever magic we do with the data, it’s useless if not printed. So we started solving Puzzle-2 (printing) before working on Puzzle-1.

How to print in Sticky Notes?

At the worst case, we can print in an A4 sheet and paste it using Cellophane tape, but that would probably be the ugliest task board ever seen (even worse than our scrum master’s hand written sticky notes). We reached out to our friend Google with few queries. How to use our regular printer to print in Sticky notes? Is there a special printer which can take in Sticky notes (instead of a regular A4 sheet) and print? If so, how much will it cost?

We came across the following YouTube video where an eBay seller demonstrated how he printed “Thank You” in Sticky notes for sending to his customers. He used to manually write in sticky notes (similar to our scrum master) and later found a way to print it as the sales went up.

 

The idea is to prepare a word page with six rectangular boxes having “Thank You” text in it. Take a printout of that word page in an A4 sheet, then paste sticky notes on those rectangular boxes and take another printout of the same page using the sheet which has sticky notes. #dadada, printer will now print the text on top of sticky notes.

If we apply this solution to our problem, our code has to create a rectangular box and insert a dynamic data (task name, task owner and so on) in that box. Code has to generate multiple boxes which may span across multiple pages depending on the number of user stories and tasks assigned to a scrum team in a sprint. Apache POI is a very popular open source Java library for editing Microsoft office documents (Excel, Word and PPT) with which we can do lot of things. But achieving everything through code (like creating a box at a specific location, dividing it into multiple sections each holding its own data, inserting data into that section and so on) is very tedious to design. If we decide to add more data or modify the design in future, it needs more changes and hence this approach is very painful to maintain as well. We have to write lot of code every time and that implies high maintenance cost in future. So we immediately dropped the plan of using code to create User Story or Task Card design.

We, the lazy smart engineers, were looking for an easier way to create neat designs for printing and easier way to modify those designs in future without pain. So, instead of coding the entire design from scratch (like creating boxes, lines and so on), if we can manually create a design using office tools and use that design as a source to generate multiple copies with appropriate data in it (for user stories or tasks), we are done!!! With this idea, we immediately switched to use PPT instead of word document.

Design the Card

Create a PPT slide. Design that slide for a task, i.e., design how you want a task to look in the sticky note. Insert Text Box and put “hint” text in places where you want to see the actual data retrieved from Rally or other data sources. Let’s say you want to see task name (which is dynamic for each task) at the top of sticky note, insert a Text Area in the top portion of the slide and put a hint text as “TaskName” (or “TN” or anything). This hint is just a reference text to use in our code. When the code copies this source slide to a destination slide, it will replace the word “TaskName” with the actual task name retrieved from Rally.

Store this PPT as Task Template file. This is a one-time work and we don’t need to change it unless we want to change the design or the data printed in Sticky note. This drastically reduces the future maintenance cost. In fact, we can change the card design without any code change as long as the data we are showing in card remains same.

Here is the Template slide we designed for printing tasks

Here is the Template slide we designed for printing Tasks

This is the Template slide we designed for printing User Stories

This is the Template slide for printing User Stories

Retrieve Data from Rally

Retrieve the list of user stories, defect stories and tasks associated with a scrum team for a sprint. Rally provides an extensive set of APIs to perform several http methods. We just need to GET information for User Stories (like Title, story points, owner and so on), Defect Stories and Tasks (like name, task owner, total hours, remaining hours and so on). Sometimes we may need to make multiple API calls to retrieve additional information (like in cases where we need to get all defects associated with a User Story) and we have done some optimizations around it to minimize the number of API calls, but it is not a big deal to explain in detail here. We just need to understand their APIs and make use of it to retrieve whatever information we want.

Write some code

Here is the logic to create task cards. Read the slide from Task Template file and read all shapes from that slide. Iterate through the list of tasks retrieved from Rally. For each task, create a new slide. Iterate though list of all shapes in the template slide and insert those shapes into the new slide. Apache POI library has options to identify shape type, text value in that shape and so on. It also provides capability to alter those shape properties if required. If the shape type is AutoShape (Text Box), we can read the hint text inside it and transform it to an appropriate data associated with that Task. When we add a shape from source slide to a destination slide, its properties are preserved and hence the generated slide looks same as the source slide except for the properties we are modifying through our code.

I have shared the code snippet below. This is for handling Tasks but the logic is same for User Stories as well. This snippet is not complete and it is intended to give a fair understanding of the implementation.

// Input - Map between User Story ID and List of Tasks associated with that User Story
private void createTaskPPT(Map<String, List<Task>> map)
{
    try {
        // Task Template file which has a slide designed for printing tasks
        String templatePPT = config.templateFileBaseDirectory + taskTemplateFileName;
        
        // PPT file to store all generated slides (a slide per task) which can then be printed
        String generatedTasksFile = config.generatedFilesBaseDirectory + teamName + taskSuffixFileName;
        
        // Read the Task Template file
        SlideShow ppt = new SlideShow(new FileInputStream(templatePPT));  // org.apache.poi.hslf.usermodel.SlideShow
        Slide[] templateSlides = ppt.getSlides();  //org.apache.poi.hslf.model.Slide
        
        // Make sure there is only one slide in the template file
        if (templateSlides.length != 1)
        {
            System.out.println("Something wrong with the Task template file - it has " + templateSlides.length + " slide(s)");
            return;
        }
        
        // Get all shapes from the template file
        Shape[] templateSlideShapes = templateSlides[0].getShapes();  // org.apache.poi.hslf.model.Shape
        
        // PPT file to store all generated slides
        SlideShow outputPPT = new SlideShow();

        // Iterate through each User Story present in the map
        Iterator it = map.entrySet().iterator();              
        while (it.hasNext()) {
            
            Map.Entry<String,List<Task>> pairs = (Map.Entry<String,List<Task>>)it.next();
            
            List<Task> storyTasks = pairs.getValue();

            // Iterate though all tasks associated with an User Story
            for (int i = 0; i < storyTasks.size(); i++)
            {
                Task task = storyTasks.get(i);
                
                // Create a new slide in the outputPPT for this task
                Slide generatedSlide = outputPPT.createSlide();
                
                // Iterate through each shape
                for (int j = 0; j < templateSlideShapes.length; j++) {

                    if (templateSlideShapes[j] instanceof Line){
                        Line line = (Line)templateSlideShapes[j];
                        // Add shape to the generated slide without any modification
                        generatedSlide.addShape(line);
                    } else if (templateSlideShapes[j] instanceof TextBox){
                        TextBox testBoxShape = (TextBox)templateSlideShapes[j];
                        // Add shape to the generated slide without any modification
                        generatedSlide.addShape(testBoxShape);
                    } else if (templateSlideShapes[j] instanceof AutoShape){
                        AutoShape autoShape = (AutoShape)templateSlideShapes[j];
                      
                        String text = autoShape.getTextRun().getText();
                        String printText = "?";

                        if (autoShape.getTextRun() != null){
                            if (text.contains("TaskName")){
                                printText = task.taskTitle;
                            }
                            else if (text.contains("Owner")){
                                printText = task.owner;
                            }
                            else if (text.contains("TH")){
                                printText = String.valueOf(task.estimate);
                            }
                            else if (text.contains("HR")){
                                printText = String.valueOf(task.todo);
                            }
                            else if (text.contains("StoryNo")){
                                printText = task.storyId;
                            }
                            else if (text.contains("TeamName")){
                                printText = teamName;
                            }

                            // Change the text value to appriate data retrieved from Rally
                            autoShape.getTextRun().getRichTextRuns()[0].setText(printText);
                        }
                        
                        // Add modified shape to the generated slide
                        generatedSlide.addShape(autoShape);
                    } else if (templateSlideShapes[j] instanceof Picture){
                        Picture pictureShape = (Picture)templateSlideShapes[j];
                        // Add that shape to the generated slide without any modification
                        generatedSlide.addShape(pictureShape);
                    }
                } // All Shapes
            } // All Tasks within User Story
        } // All User Stories
        
        // Write the output ppt file for tasks
        FileOutputStream out = new FileOutputStream(generatedTasksFile);
        outputPPT.write(out);
        out.close();
        
    } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
Add Sugar Coating

Let’s look at some sugar coating we have added on top of this basic implementation.

  1. CQ engineers’ task names are displayed in grey background for easy identification.
  2. Each scrum team member’s name is displayed in a specific background color of their choice for easy identification and to bring personal attachment with the task board.
  3. Defect Stories are printed with defect title in red background.
  4. If a Story name or Task name goes beyond a certain length which we cannot meaningfully print in the Sticky note space, we trim it and show three dots.
  5. Option to automatically identify the current sprint number, start date and end date.
  6. And so on.

With just a slight extension to the core logic, we have achieved all these sugar coatings. For e.g., to display each team members name in the color of their choice, we have created a configuration file where we maintained each scrum team member’s name and RGB code of the color they like. When we replace “Owner” hint text with actual owner name, we will also read color preference of that person from the configuration file and set it as background color. It’s that simple.

else if (text.contains("Owner")){
    autoShape.setFillColor(getColor(task.owner));
    printText = task.owner;
}
See the output
Here is the Slide generated for a task

Here is the Slide generated for a Task

Slide generated for a User Story

Slide generated for a User Story

This is generated for a defect story

This is generated for a Defect Story

Isn’t it attractive?

Let’s print now

Now that we have generated PPT files for User Stories and Tasks, next step is to print those slides. PPT has an option to print one or multiple slides per page. We use 50×50 size sticky notes for printing tasks and hence 6 sticky notes can be pasted in a single A4 sheet. So we use 6 slides per page print option for printing tasks. Similarly 100×50 size sticky notes are used for printing User Stories and two slides per page print option works.

We need to decide on the box area in which a Sticky note will fit while printing (for either 6 slides per page or 2 slides per page). Identifying the box area is relatively straight forward. We can put multiple boxes of varying sizes in a slide, print it and check which box will fit a sticky note of size 50×50 for Tasks and 100×50 for User Stories. Same box area has to be used when designing the template slide as well (that is the reason you are seeing different design for Task Template and User Story Template above).

 

Have any questions or comments, please post it in comments section below.

Your email address will not be published. Required fields are marked *

*