Skip to: Site menu | Main content



Running JSLint in Automated Build Scripts

At VendAsta we use automated build scripts and a TeamCity server for continuous integration. Whenever someone commits code to a project, a whole suite of automated build targets are run against the code base to check things like unit tests, python syntax (via pylint) and code coverage. One thing that was missing from the mix was the automated running of jslint to check the syntax of our ever-increasing corpus of JavaScript code. As it turns out, adding this to our ANT targets wasn’t all that difficult.

The only real difficulty you are likely to encounter in creating a JSLint target for your build scripts is that JSLint is, itself, written in JavaScript. Other helpful libraries such as JSMin or the YUICompressor from Yahoo are distributed as jar files and can be executed easily with an exec block with the executable set to java. With JSLint, however, you are going to need a JavaScript engine which you can execute from the command line in order to run the JSLint script to validate your JavaScript code. THis actually sounds harder than it is.

For our implementation, I chose to use the Rhino engine since there is documented support for JSLint on it, and even a handy helper file to include in your system: rhino.js. Grab a copy of Rhino (the actual file you’ll want to include is js.jar), the Rhino helper script I just mentioned and JSLint itself. Place them all together into some sort of tools directory in your project. Ours is located in /tools/ant.

Now comes the fun part, writing the actual target in ANT. For our purposes, we want to process all .js files contained in a particular location, as defined in a build property called jsdir. We also want to ignore any files that are contained in a vendor subdirectory of jsdir, as we should not need to worry about third-party scripts that don’t pass JSLint. Let’s get things started with our basic, empty target:

1
2
3
4
5
6
<!-- 
jslint - Runs lint checks on js files
-->
<target name="jslint">
 
</target>
Listing 1: The basic, empty jslint target

Within the target there are two things we are going to need. First a way to reference all of the files we want to operate over and second, the actual call to run JSLint on those files. For the first requirement we’ll use a pathconvert tag with a nested fileset. The pathconvert will allow us to generate a space-separated list of files and stuff that into a property which we’ll call jsfiles. The fileset block will do the actual work of finding the files in question. It will operate over ${jsdir} including all .js files and excluding all .js files in the vendor subdirectory. Listing 2 shows the pathconvert block added to our empty target.

1
2
3
4
5
6
7
8
9
10
11
<!-- 
jslint - Runs lint checks on js files
-->
<target name="jslint">
    <pathconvert pathsep=" " property="jsfiles">
        <fileset dir="${jsdir}">
            <include name="**/*.js"/>
            <exclude name="vendor/**/*.js"/>
        </fileset>
    </pathconvert>
</target>
Listing 2: The pathconvert block added to our target.

Finally, it is time to add the actual call to JSLint. To allow our build scripts to work for multiple projects, we have pushed as much information into properties that get defined in project-specific files as possible. The location of the Rhino jar (js.jar) is placed into the property js.jar and the location of the JSLint script (jslint.js) is placed into the property jslint.js. With those properties suitably defined elsewhere, we have the final piece of our puzzle: the exec block. We have the executable set to java and failonerror set to true, so that our target will appropriately fail when JSLint fails to give our code a passing grade. The final target, including the exec block can be seen in listing 3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 
jslint - Runs lint checks on js files
-->
<target name="jslint">
    <pathconvert pathsep=" " property="jsfiles">
        <fileset dir="${jsdir}">
            <include name="**/*.js"/>
            <exclude name="vendor/**/*.js"/>
        </fileset>
    </pathconvert>
    <exec dir="${jsdir}" executable="java" failonerror="true">
        <arg line="-jar ${js.jar} ${jslint.js} ${jsfiles}"/>
    </exec>
</target>
Listing 3: The final target.

Put all together, the pathconvert block stuffs the list of files to work on into the property ${jsfiles} which is then passed as an argument to the jslint.js file. The jslint.js file is itself the first argument passed to the Rhino JavaScript engine (js.jar) which is run directly by java. Running your ant jslint target should give you some nice feedback on the syntax of your JavaScript code.

Another New Spokesmonster Video

No one seems quite sure which episode this is, but here it is:

Excellent work Michael and Nicole!! I think it could have used a little more of Dave’s vocal stylings though ;)

WTF?

Roger’s Wars Episode 6: Return of the Mad Guy

This is part 6 of a multi-part post on my battle to get an iPhone for myself and a Blackberry Pearl Flip for my wife from Roger’s wireless. At the time of this writing, the saga is still on-going so I cannot say how many episodes there will be… Previous episodes: Episode 1: The Phantom iPhone, Episode 2: A Lack of a Phone, Episode 3: Revenge of the Pissed, Episode 4: A New Phone, Episode 5: The Empire Bills Back.

Thus far in my struggles to get an iPhone, all of my problems have been in getting the phone itself and in my dealings with Roger’s on billing and such. I had not had any problems at all with the phone itself. Considering the way everything had gone with the whole ordeal, I guess I should have expected there to be something in the works for me.

It all started with my wife accusing me of ignoring her. Several times she had called me and I didn’t answer my phone. I really was not ignoring her, I just thought that I had my ringer turned down too low. I would wake my iPhone from its slumber to find a missed call. Or two. Or three. And voicemails. I tried turning up the ringer volume. I even tried setting the ringer for my wife to be a loud klaxon alarm. Nothing. I told her that I just wasn’t hearing it when she called. She didn’t believe me so I tried an experiment - I placed the phone on the table in front of her and called it from her cell phone. It rang away in my ear but sat silent before us. As soon as I woke it, it registered a missed call.

Now, being the kind of guy that I am and dreading having to speak to anyone at Roger’s, I decided to do some searching for the problem. Fairly quickly I discovered that there were two possible issues. One was just a software glitch that could be solved by doing a reboot of the phone and the other, a hardware problem affecting a certain percentage of Canadian iPhones, would require replacement. I shut down my phone to see if I was lucky enough to have the software version of the problem and while it was restarting, I dug into the box it was shipped in to see if there was a special number to call for technical issues. What I found in the box was far more troubling than the phone not ringing while asleep.

My iPhone is a refurbished one. I was originally sent a brand-spanking-new one, but it was stolen en route to me. Since I received my replacement phone through the lost and stolen program, they sent me a refurbished phone. I knew this when I received it but I didn’t really care. First of all, I was very happy to finally have the phone in my possession. Secondly, I understand how refurbished hardware works. In fact, the MacBook Pro I am using to write this is refurbished. Refurbished machines can be anything from returned in an open box but never used to machines that had some small problem that was then repaired. Refurbished products are usually heavily tested and checked and in most cases they are just as good as new. What I didn’t notice upon first receiving the iPhone was the small red card in the box that now grabbed my full attention. This little red card stated that my refurbished iPhone came with a 6 month warranty.

It was that small red card that prompted me to call Roger’s once again far more than it was anything to do with the not ringing issue. In fact, since that reboot my iPhone has never failed to ring while asleep. Not once. In all likelihood it was something caused by an app I had installed and not rebooted afterward. I’m a bit of an App Store whore and I am loath to ever turn my phone off. Nevertheless, I called in to Roger’s to see if anyone there could kindly send me a brand new phone with a full 1 year warranty, since that was what I paid for.

I dialed the hated toll-free number yet again and rambled my number (starting with the area code, always starting with the area code), name, postal code and birth date. I was connected to a woman in the customer service department. She had a moderately thick Spanish accent but she seemed to be understanding what I was telling her. I explained my saga and how it led me to have a refurbished phone. I explained about the ringing troubles and how it led me to discover my truncated warranty. She told me that I should talk to technical support but that they were not open as this was on the weekend. I explained again to her that I was not really calling about the technical problem, I was calling her because I wanted a brand new phone which is what I had paid for and I wanted my full 1 year warranty. Again she told me that I needed to talk to technical support. Again I explained to her why this was not a technical issue. I told her I wanted a brand new phone. At this point something new happened. Something which had not happened to me in my dealings with Roger’s. She started yelling at me.

I’m sure that she now realizes that this was a mistake. She started yelling into the phone that I had already had my upgrade and I could not get another one for a year. Surprisingly, I remained calm. Once again I tried to explain the situation to her - that I had paid for a brand new phone with a 1 year warranty and that was what I wanted to be provided with. She continued to be very loud and very rude to me. I believe it was at this point that Lindsay set her Facebook status to, “Listening to my husband tear someone at Roger’s a new one”. I ripped into her. I really let her have it. I unleashed all of my pent-up frustration at Roger’s and at being on the phone with one of their mindless fools for the umpteenth time. I ended my tirade with a demand for a supervisor. I am not sure how it actually sounded, but to me, hearing myself raging into the phone it was the voice of a god demanding obedience. It probably wasn’t anything that dramatic, but she told me to hold and went away.

Were it not for the accent I would have thought it was another woman who came back on the line. The essential gist is that she could not send me a new phone (I am guessing that her supervisor listened to a recording of the conversation and explained it to her properly), but that the customer relations department sometimes sent out new phones as replacements so that they might be able to help me. She apologized several times and I just got the hell off the phone. The customer relations department was also closed on the weekend.

Monday morning I called back in. A peon in customer service asked for my details and I said not to bother, just transfer me to the customer relations department. He said he needed the information to pull up my account. I asked him why he needed my account when he was just going to transfer me away? He had no answer and transferred me to Zed. Zed was a very smooth operator. I spoke to Zed for a while, telling him my whole long story and he hummed and hawed at the right times, murmured “that’s not right” a couple of times and then, when it was all done, told me that I couldn’t get a new phone through him, but maybe technical support could send a new one to replace the one I had if it was broken. He said he was going to transfer me to the Technical Support department, but I stopped him.

“Zed”, I said, “you’ve just listened to my whole sordid tale and I’ve been through a lot. The woman I spoke to on the weekend was the rudest, least helpful person I have encountered in a tortuous saga of poor customer service. I have been given many account credits to compensate me for much less than the abuse she sent my way this weekend and I want to know what you’re going to do about it.”. He sounded genuinely confused and asked me what I meant. I told him flat out that she had treated my horribly and I wanted him to put some “goodwill” credits on my account to atone. Unhelpfully, he told me that I had gotten too many credits already and he couldn’t give me any more. I told him that was fine…someone else would if he wouldn’t. He then transferred me to the technical support department.

As expected they told me that they could not swap out my phone. They did give me some ideas on what I could do if the phone continued to behave badly. Then they attempted to transfer me to the customer relations department, as they could most likely help me. Unfortunately there appeared to be a technical issue with the technical support department and my call was dropped.

I was really tired of the automated phone system at this point. I called back in and all I would say to it when it asked me what I wanted was “VINDICATION”. Three “VINDICATION”s gets you to the customer relations department, in case you were wondering. I don’t recall the name of the woman I then spoke to, because it was far less memorable than Zed. She did, however, seem to care about strengthening the relations between Roger’s and this particular customer far more than Zed, despite lacking all of his slick talking ability. She explained to me that the reason I could not get a new phone was because the UPS investigation was not completed yet. Since that had not been resolved, the system thought that I had already received a phone through the hardware upgrade process and would not allow me to get another new one for a full year. She told me that I needed to followup with UPS on the investigation and once it was done I should notify Roger’s and they could then clear my account and send me out a new phone. I told her that it was not my responsibility to contact UPS as that was between Roger’s and UPS and had nothing to do with me. She agreed and said she’d make some notes and send some emails to get things moving on their end. That pleased me.

The Score So Far

  • Three months unlimited local calling.
  • Three months unlimited data usage.
  • $10 credit / phone / month for 36 months.
  • $50 goodwill credit
  • $25 goodwill credit
  • $50 goodwill credit

This new woman was also far more sympathetic when it came to the woman I had dealt with on the weekend. Instead of promising me that the woman would get a “coaching session” to learn from the experience, she put a $50 credit on my account. That was more like it. Finally some results. They were going to chase down the UPS investigation and I’d get a new phone when it was done. My phone in the meantime was now ringing when it should and I had another credit on my account. I thought that I might be able to see this horrid saga drawing to a close. If only that had been true. If only there had been more Star Wars movies from which I could draw titular inspiration. The next episode will see more billing shenanigans, more UPS investigation fail, more account credits and something new with my titles!

25 Things About Me

Rules: Once you’ve been tagged, you are supposed to write a note with 25 random things, facts, habits, or goals about you. At the end, choose 25 people to be tagged. You have to tag the person who tagged you.

If I tagged you, it’s because I want to know more about you!

(To do this, go to “notes” under tabs on your profile page, paste these instructions in the body of the note, type your 25 random things, tag 25 people (in the right hand corner of the app) then click publish.)

  1. I was born in Nova Scotia
  2. I have never felt more at ease than when I was in Scotland
  3. I am going to Mexico in 22 days
  4. I miss making music
  5. I like it loud
  6. Except when I don’t
  7. I am a solid convert - to Macs
  8. I am a proud Atheist
  9. I usually need spell check to spell Atheist correctly
  10. I love sci-fi
  11. I have an awesome family
  12. Boxer briefs
  13. I love chick-rock
  14. I might be an author today if not for one English teacher
  15. I miss writing
  16. I actually enjoy programming
  17. I hate to admit it but I dig Lindsay’s One Gossip Tree Girl shows
  18. I am a whore for documentaries
  19. I am intrigued by Eve online
  20. I miss playing WoW
  21. I am an introvert…
  22. …but I fake extrovert fairly well…
  23. …or maybe I’m bipolar
  24. I think I could do a better job than our Government or any of the lame other parties we have
  25. I wish I could spend more time with my wife and daughter.

Monitor and Manage Your Rep With StepRep

I work with an amazing group of people. The project I am working on is called MyFrontSteps which is a social media application for getting and sharing Trusted Referrals to service providers in your area. We’re working on some pretty challenging stuff and, even though we are progressing rapidly, we are not quite to the point where we have anything public-beta worthy. Our colleagues at the far end of the building, the StepRep team, have reached their public beta and it is really impressive!

Whereas the side of the project I work on is focused on dealing with home owners, StepRep is a tool for Online Reputation Management, primarily targeted at the service providers that our system will help you recommend to your friends. Of course, it isn’t just people in the home improvement or real estate industries that have online reputations they need to manage. In fact, software developers and hardcore geeks have online reputations! I have an online reputation! That’s why I signed up for the beta at http://steprep.myfrontsteps.com. I know that I am a little biased by the fact that I work with the people that developed all of this, but I am very impressed with what they have done. From a technical standpoint there is a lot going on that even I find overwhelming and a little “magical”.

My Personal StepRep Widget

The main purpose of StepRep is to allow you to see what people are saying about you and your business on the Internet. This includes anywhere and everywhere that someone might be talking about you. StepRep presents you with this information and allows you to categorize the items as good or bad. The good ones are things that you can use to convince new people that you are the right choice for the work they need done. You can even place these items into a widget that can be slapped on your company website, your personal blog - anywhere you want to trumpet your achievements and build your reputation. As for the items you think reflect poorly on you, well, obviously you wouldn’t want to put them in your widget, but you can see where past customers might not have been completely satisfied and do something to change their mind. Flip them to a positive and then slap them in your widget for all to see.

I won’t even try to discuss anything about how StepRep does what it does so well. Honestly, this is because I don’t understand a lot of it. The team working on it are very smart and very skilled at what they do. Starting with the next development sprint my team is going to be working closely with that team as we bring the two systems together to help service providers reach a wider audience through the friends of their customers and to help home owners find the best service providers through people they trust. It’s going to be very exciting and I am hoping that I’ll be able to learn a few things about how StepRep works.

One last thing I want to mention is how much I love the design work that was done for the StepRep site. I got to see some preliminary designs for the MyFrontSteps site today as well and they blew me away. Marie-Louise has really done an awesome job. This is something I really love about working at VendAsta. I work with people who are very skilled at designing for the web and for maximum usability. The wireframing, mockups and designs that I have seen done in my short time there have amazed me. I came to VendAsta from a company that had plenty of graphic designers in the Marketing department. They were all firmly rooted in the world of print advertising. There were some definite growing pains going on as they started to embrace the new world of web design. Now I am working with people who understand the web and the best practices for developing rich, engaging applications for the web. It’s freaking awesome!

Splitting Django Models Into Separate Files

A little while ago I wrote a post about getting Django unittests to work on DreamHost. The particular problem with the DreamHost setup is that they don’t allow you to programmatically create and drop databases and the default Django unittest behaviour is to do just that: create a brand new test db, use it and then drop it. Since I wrote that post I have begun migrating all of my domains away from DreamHost but I have still found that technique to be useful with new projects.

I first started using Django when I started at VendAsta and this issue didn’t creep up there because we are use Google App Engine and its DataStore on the backend instead of a traditional database. With no database there was no issue with creating/dropping anything. It was only when I started using Django on my own that I came across that problem. There was one more issue with Django that I came across only when I started doing things outside of work: Django expects all of your models to be in a single file called models.py. I hate that. A lot.

I did some googling about the problem and found that there were a bunch of other people who didn’t like it much either. There were a few solutions but they all involved manually mucking about with the __all__ attribute in the __init__.py file of your models directory. Sure, it works, but I don’t like having to define a model in two places to use it. Oddly enough it was in some test code at work that I found the answer.

For our project at VendAsta we are using the Django App Engine Helper to provide a nice easy interface to the DataStore on the backend. A part of this is a small __init__.py file which allows you to spread your tests out in multiple files. Django likes its tests to be in a single tests.py file just like it likes its models to be in models.py. The wee bit of Google code is shown in Listing 1.

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
#!/usr/bin/python2.4
#
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
 
 
 
 
"""Loads all the _test.py files into the top level of the package.
 
This file is a hack around the fact that Django expects the tests "module" to
be a single tests.py file and cannot handle a tests package inside an
application.
 
All _test.py files inside this package are imported and any classes derived
from unittest.TestCase are then referenced from this file itself so that they
appear at the top level of the tests "module" that Django will import.
"""
 
import os
import re
import types
import unittest
 
PACKAGE = 'appname.tests'
TEST_RE = r"^.*_test.py$"
 
# Search through every file inside this package.
test_names = []
test_dir = os.path.dirname( __file__)
for filename in os.listdir(test_dir):
  if not re.match(TEST_RE, filename):
    continue
  # Import the test file and find all TestClass clases inside it.
  test_module = __import__('%s.%s' % (PACKAGE, filename[:-3]), 
                           {}, {},
                           filename[:-3])
  for name in dir(test_module):
    item = getattr(test_module, name)
    if not (isinstance(item, (type, types.ClassType)) and
            issubclass(item, unittest.TestCase)):
      continue
    # Found a test, bring into the module namespace.
    exec "%s = item" % name
    test_names.append(name)
 
# Hide everything other than the test cases from other modules.
__all__ = test_names
Listing 1: __init__.py file from Google for a tests module

Essentially what this file does is loop through all files in the same directory and for each on that matches the pattern listed on line 40 (in this case, files that end in ‘_test.py’) and import all of the Classes from those files that are subclasses of the TestCase class. Finally, the code on line 62 does the magic with __all__ that I wanted to avoid doing manually for my models. It was this line that caught my attention and started me wondering if this code could be used for spreading models across multiple files.

In order for this to work for models there are a few things that need to be changed. First of all, on line 40, the pattern should be changed to match on *.py to grab all of the python files in the directory. Then, on line 46, add

or filename == “__init__.py”

to ensure that we don’t try to import the file we are in. Finally, remove the subclass requirement from line 55, as we are just concerned with importing classes here not what they inherit from. Putting it all together results in Listing 2. Place that in the __init__.py of a directory called models and you’ll not have to manually manage the __all__ list for your models in separate files.

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
#!/usr/bin/python2.4
 
import os
import re
import types
import unittest
 
PACKAGE = 'my_app.models'
MODEL_RE = r"^.*.py$"
 
# Search through every file inside this package.
model_names = []
model_dir = os.path.dirname( __file__)
for filename in os.listdir(model_dir):
  if not re.match(MODEL_RE, filename) or filename == "__init__.py":
    continue
  # Import the model file and find all clases inside it.
  model_module = __import__('%s.%s' % (PACKAGE, filename[:-3]),
                           {}, {},
                           filename[:-3])
  for name in dir(model_module):
    item = getattr(model_module, name)
    if not isinstance(item, (type, types.ClassType)):
      continue
    # Found a model, bring into the module namespace.
    exec "%s = item" % name
    model_names.append(name)
 
# Hide everything other than the classes from other modules.
__all__ = model_names
Listing 2: __init__.py file modified for a models module

Now, there is one last thing that you are going to need to do in order for everything to run properly in Django. It is a very simple addition to the end of each of your classes. I have seem some people complaining about the need to do this in various forums, but honestly, if you are using any form of modern text editing application, it should only take you about 2 minutes to create a new template for a model that has the little piece of code shown in Listing 3 at the bottom of the class (replace your_app_name with your actual app name).

1
2
3
class Meta:
        """Meta Class for your model."""
        app_label = 'your_app_name'
Listing 3: Meta class required at the end of each model for proper functioning.

Roger’s Wars Episode 5: The Empire Bills Back

This is part 5 of a multi-part post on my battle to get an iPhone for myself and a Blackberry Pearl Flip for my wife from Roger’s wireless. At the time of this writing, the saga is still on-going so I cannot say how many episodes there will be… Previous episodes: Episode 1: The Phantom iPhone, Episode 2: A Lack of a Phone, Episode 3: Revenge of the Pissed, Episode 4: A New Phone.

I look back on those first few days with my iPhone the way I look back on a beautiful summer day from the dead of winter. I finally had my new phone and it was awesome. There was nothing I needed to call Roger’s about, nothing I was waiting for. I was downloading apps, checking email, surfing the web, tweeting…it seemed like it had all been worth it. Those days came to a quick end when my next bill arrived.

Now, I was not expecting my bill to be perfect. I knew that there were going to be some issues as the credits which had been applied to my account had been done so after my bill was printed and placed in the mail. What I wasn’t expecting, however, was it to be so very wrong. The main issue was that the charges for the hardware upgrades on both phones were significantly higher than they should have been. It looked like the credits which were supposed to go on my account when I added data plans were not actually added. So, the charge for my iPhone was $50 more than it should have been and the BlackBerry was $250 more than it should have been. It was time for yet another call to Roger’s.

Eleven digits, some one-word answers to the automated assistant, phone number, name, postal code and birthdate. The whole sordid story recounted for yet another front-line Roger’s grunt on the phone. I explain that I was missing the credits on each phone that should have been applied when I added my data plans. Some humming and hawing on the other end of the line assured me that there was, indeed, someone listening to what I was saying. I was expecting a quick resolution. After all, the prices for the phones were listed all over the Roger’s website, how could they argue? I finished my tale and politely asked the man I was speaking to to fix things for me.

“I’m sorry, but you aren’t eligible for those credits. Those credits must be applied at the time the phones were purchased. You need to order the data plans when you order the phones to get those credits.”

Now, before I explode here, a little flashback. Way back in Episode 1 the very first Roger’s employee I spoke to advised me (in fact, completely refused to discuss data plans with me) to not order my data plan until I had the phones in my hand. Otherwise, he said, I would be paying for my plan without having access to the service. It sort of made sense, in a way, but I figured that they must have some way of putting a plan on my account but only activating it when the phones were activated. That was the reason for my call back a few minutes later, starting this whole saga on its way. Over the course of the weeks that followed, this same advice was given to me by several more Roger’s employees, including one Supervisor who went through the trouble of putting all of the details of what I wanted into the memo of my account…details that were magically unavailable when it came time to really order the data plans. I’m digressing into past episodes, but the important message is that they repeatedly told me to not order my data plans until I had both phones in hand and being activated.

Dark Side Descent Part V
An Epidemic?

It was well before this whole story began that I began to notice how good customer service is becoming harder and harder to find these days. This might be limited to Saskatoon as our city is growing faster than the job market can keep up with. Fast food joints were the first places where it really became obvious. They have never been known for good service, of course, but in recent months it has become much worse. As wages have increased to keep pace with the demand, everyone seems to have shifted up a level. People who used to work fast food, you know, the low-end of the teenagers willing to work, have moved on to the retail positions that the more intelligent/friendly teenagers willing to work used to fill (they have moved on to yet higher positions). This gap is now being filled by those teenagers who were never motivated enough to work before (but the wages are high enough to attract them now) and were never desired by employers (but beggars cannot be choosers now). Perhaps this is happening all across the country. Maybe this is what has happened at Roger’s.

All of this has made me wonder if we need to re-adjust our thinking when it comes to service. Tipping, for example, used to be reserved for service that was above par. It was a reward for exemplary service. Recently tipping has been seen as a necessity and not a reward that service providers should work hard to try and earn. Perhaps this needs to change. Could that help turn the tide of poor service?

The Score So Far

  • Three months unlimited local calling.
  • Three months unlimited data usage.
  • $10 credit / phone / month for 36 months.
  • $50 goodwill credit
  • $25 goodwill credit

I exploded. I raged. I ranted. I informed the guy I was speaking to that I had been trying to order a data plan from the very first moment this whole deal began and I was told again and again to wait. So I waited. Then I had the phones in my hand, and I called in and activated them. I ordered my data plans. Now, I wanted my credits. There was no way I was going to pay more for these phones than every other Roger’s customer because I had actually followed their instructions. Once again it was time to speak to a supervisor.

This was one of the bad offices I was on the line with. When you call the Roger’s 888 number you get routed to one of their call centres. There really isn’t any way to tell them apart or tell where exactly they are. You can, however, tell that there are multiple centres by the on-hold music they play. There’s one that plays a short snippet of classical-esque music on a constant loop. Another plays a loop of some pseudo-rock music. The worst plays a loop of a Michael Bolton album. Specifically, Time, Love & Tenderness, released in 1991. Before you even think it, no, I am not a Michael Bolton fan. Far from it. However, I have heard that album enough times on hold with Roger’s to track it down on Amazon. Sad. Infuriating.

After between ten and fifteen minutes of music which was probably intended to sooth angry people, I was beyond angry. I was so tired of dealing with Roger’s and so very tired of Michael Bolton. The supervisor came on the line and, of course, they had not used any of the time I had spent waiting to pass along any details of the situation. Once again I had to explain everything that had gone on and was going on now. Thankfully, this time the person on the other end of the line understood and realized that the current situation was their fault and not mine. Credits were placed on my account to bring both phones down the the advertised prices. I didn’t even need to ask and a $25 “goodwill” credit was placed on my account as well, for “all of the trouble”.

I wish that this was the end of my billing problems with Roger’s but it wasn’t. That’s another story for another episode. Not the next episode, however.

Update 02/10/2009: Episode 6: Return of the Mad Guy has been released.

Roger’s Wars Episode 4: A New Phone

This is part 4 of a multi-part post on my battle to get an iPhone for myself and a Blackberry Pearl Flip for my wife from Roger’s wireless. At the time of this writing, the saga is still on-going so I cannot say how many episodes there will be… Previous episodes: Episode 1: The Phantom iPhone, Episode 2: A Lack of a Phone, Episode 3: Revenge of the Pissed.

It was almost noon when the text message arrived. It was from my wife. All it said was “Happy new phone day, honey!”. Finally, FINALLY! After nearly a month I finally had an iPhone! It had been a little over a week since I was promised a phone through the “lost and stolen” program and someone at Rogers had finally delivered on a promise and UPS had finally delivered a box full of iPhone. It had been a long, long month. I think I spent as much time talking to Rogers as I spent talking to my wife. It was finally done, my ordeal was over!

Okay, yeah, reality check. The ordeal was far from over. At the time, however, I thought it was over. I’m going to keep that delusion going for a moment now and talk about the iPhone. I love my iPhone. This is 100% to the credit of Apple. They have made a great phone and I couldn’t be happier with it. Well, I would be a little happier if Rogers’ G3 network wasn’t complete and utter shit, but I’m taking a break from talking about Rogers. Ever since I started working at VendAsta I have been a complete Apple fanboy. I was uncertain about whether or not I would like working on a MacBook Pro, but I have to admit that I love it. I am fully aware that some of my iPhone enthusiasm is a side effect of my new fanboy status. Nevertheless, I have found the iPhone to be a very well designed and implemented device. There are lots of reviews on the web for the iPhone, so I won’t go any further into that but I definitely recommend the iPhone. Just try and get it through anyone other than Rogers. And Michael, I have had absolutely zero troubles using the iPhone out in the cold. It works like a charm inside or out in -40C.

With my iPhone in-hand, all I had to do was make one final call to Rogers to get it and the BlackBerry activated. I dialed the hateful toll-free number for what I hoped to be the last time. I belched out my phone number, name, postal code and date of birth to what had to be the last time. I was connected to a young-sounding gentleman who sounded really eager to help me. We went through the activation and everything seemed to be good to go. He even managed to reassign the SIM cards in the phones so that I could put the SIM card that shipped with each phone into the phone it came from. Everything seemed to be going swimmingly!

With the activation complete we began to discuss my compensation for all of the trouble I had gone through so far. I told him that I had been given 3 months of unlimited local calling for four days worth of inconvenience so what could he do for the two weeks that followed? What he said next was a pleasant surprise. It appears that my raging had really made an impression on the last guy I had talked to. He had placed a $10 / month / phone credit on my account through 2011 - for the full 36 months of my contract! I was very pleased. I said to the guy, “Well then, everything is all taken care of. I got this credit for the past two weeks and the 3 months of unlimited calling for the original four days.”. “Well…”, he said. Oh shit. So it turns out, the three months of unlimited local calling that I was told was only available to new customers is actually something that everyone gets - upgrades and new customers. Sigh. So then, I asked the guy what he was going to do for me? I was now compensated for the past two weeks of trouble but now I needed to be compensated for the original four days of hell. Three months of unlimited data usage ought to do it. My rage compensated, my phone activated, I happily ended the call. It felt good to have it all behind me.

I brought my wife her BlackBerry and we started playing with our new toys. Even with two email and Internet-enabled smartphones, we were eager to try out text messaging on the new devices. Lindsay and I have been texting each other incessantly almost since the moment we met…but that’s another story for another saga. I entered her phone number and the profound message “hi” into my iPhone and hit send. Just like in iChat there was the little woosh noise of a sent message and the other woosh of an arriving message. I had sent the message to my wife’s number and it had been delivered to my phone. Sigh.

Dark Side Descent Part IV
Collateral Damage

After the events of this episode and the rewards I had gained by being demanding and persistent, I started to see some opportunities elsewhere. I had heard through a former colleague that by calling Bell and demanding a better rate it was possible to actually get a better rate. You just needed to ask for the Customer Loyalty department. Prior to the beginning of this saga I probably would not have actually called up a company I was doing business with to demand a better deal. Now, however, it was a different story. I called Bell, asked for the Customer Loyalty department and after 20 minutes on the phone I was rewarded with a 30% reduction in my monthly bill. Just because I called them, and demanded something better. If you are a Bell customer, give them a call and see what you can do!

The Score So Far

This episode has seen some significant gains in the compensation being offered to me for having to put up with all of the crap Rogers serves up, here’s where things stand:

  • Three months unlimited local calling.
  • Three months unlimited data usage.
  • $10 credit / phone / month for 36 months.
  • $50 goodwill credit

I figured that the magical reassigning of SIM cards had not actually happened but I wanted to be sure - the guy had said something about the reassignment ensuring that the right data plan was on the right phone so I wanted to make sure that we would both have what we needed. I dialed again. I spouted again. Phone number. Name. Postal Code. Date of birth. I explained the situation. I was told that a SIM card cannot be reassigned to another number. I was further told that the data plans are assigned to the phone numbers, not SIM cards. I was told to swap the SIM cards back and try things again. I did. I texted. Messages arrived where they were supposed to be. The new guy asked if there was anything else he could do for me. I said nope, unless he wants to give me more free stuff. He said sure and put a $50 credit on my account.

So, I had my phone and a few credits on my account. Unlimited data usage for three months to get a feel for my data usage requirements. It seemed like things were finally coming together and now all I had to do was enjoy my phone, right?

Update 01/21/2009: Episode 5: The Empire Bills Back has been released.

Update 02/10/2009: Episode 6: Return of the Mad Guy has been released.

Reflections on MFS Sprint 1

Today at VendAsta we had our first full day of planning for the MyFrontSteps Sprint #2. We did some high level planning yesterday as well as demoing and a post mortem for the first Sprint. Having had a chance to relax this evening watching a few episodes of Long Way Round with my wife, there are a few things that have struck me about the whole Scrum process as it works at VendAsta.

First and foremost, I absolutely love the amount of planning and forethought that goes into each 30-day development sprint. My past two jobs had nothing that even remotely compares to this. One was an online grocery store (which just recently discontinued operations) which meant that most development was done within the demands of an operating e-business and as a result needed to be very seat-of-the-pants. The other was a manufacturing company which meant that development processes were heavily driven by the demands of sales and manufacturing departments and those demands could and would change in the blink of an eye. Needless to say, I have found the change to be very liberating.

That is not to say that we couldn’t do more planning. One of the things that we found working through our first sprint was that while we felt we had done a great deal of planning up front, there was actually a lot of granularity that we were lacking. This is something we are endeavoring to overcome in the planning for Sprint 2. As much as I love the planning sessions we are having and as exciting as I find the whole process, it is a very tiring and draining experience. Well worth it, but wearying nonetheless.

A second major thing that struck me was how well we worked as a team. Considering the fact that five people were placed on a development team, given a couple of weeks to do some research and get to know each other and then thrust into the middle of a very ambitious project, we somehow managed to work very well together. Coupling this with the fact that we are working with a large number of cutting edge technologies, I would have expected at least a little friction to develop somewhere in the team over the course of 30 days, but none did. I believe that this is largely due to the environment at VendAsta and their hiring process. Everyone who gets hired there has to go through a lunch with other developers whom they will be working with. If those devs don’t think that you are a good fit for the team and the company, you don’t get hired. I don’t know why more companies don’t operate this way. People from HR departments should use VendAsta as a case study in how to build a whole company that just “gels”.

Finally, in the past two days I have been very impressed with the manner in which some of the trouble spots from Sprint 1 have been discussed. Previously, I have experienced project post mortems that are nothing more than a gathering of people around a table to point fingers at each other. In our post mortem yesterday we were encouraged to be completely honest and frank about what we did and didn’t like about how Sprint 1 went. At no point was there any finger pointing and the blame game just didn’t happen. The maturity of my team mates and our “chickens” impressed me to no end.

So, that is about it for my impressions on Sprint 1. We’re neck deep in planning Sprint 2 right now so I am afraid that Episode 4 of Roger’s Wars will still be a couple of days away. I’ve had a few people contact me asking when the rest of the saga was going to unfold but with the holidays and now our sprint planning, I’m having trouble finding the time to do it when my head is not either a) jell-o or b) utterly focused on work. Tomorrow or the next day I hope to sit myself down with a pot of Earl Grey and hammer out the fourth installment. Thanks for your patience :)