Automate personalising and sharing documents with Apps Script

As a manager I spend a fair amount of time in… Google Docs!

While I don’t mind writing things which are not code, I do dislike tedious tasks… specially tasks that can be automated. One of these is creating separate documents per person using a template. You have to open the template, make a copy, change things, share it with the report… TEDIOUS! 😤🙅🏻‍♀️

Fortunately, there’s a solution to automate this: Apps Script!

It exposes the same apps you’re used to interact with, but programmatically. And so I translated the sequence from above into code. I can now do in 22 seconds what used to take me 15 minutes and a considerable amount of frustration building. And because I’m VERY NICE I’m sharing the code with you, so you can also save time and do better things with your brain. You’re very welcome 💓

Apps Script is basically JavaScript, but it runs in The Cloud. To access the dashboard you need to be logged in with a Google account and then go to https://script.google.com/. Then create a new project and paste the following in the editor:

var docs = [
  // The id is the string you see in the document url:
  // https://docs.google.com/document/d/{the very long string here}/edit
  { id: 'documentID1', title: 'PERSON:Your name 1:1 - YYYY' },
  { id: 'documentID2', title: 'PERSON YYYY goals' }
];

var people = [
  { name: 'Person number one', email: 'personnumberone@example.com' },
  // More people if you need
  // I recommend you test with your own address before sending to everyone!
];

function main() {
  people.forEach(function(person) {
    docs.forEach(function(item) {
      var newTitle = personalise(item.title, person);
      var fileClone = clone(item.id);
      var clonedId = fileClone.getId();
      var docClone = DocumentApp.openById(clonedId);
     
      fileClone.setName(newTitle);

      // Replace template tags
      personaliseDoc(docClone, person);

      // Share with person's email, read+write
      fileClone.addEditor(person.email);
    });
   
    // Let them know why you sent them a bunch of docs out of the blue
    // (specially if you're sending many!)
    GmailApp.sendEmail(person.email, "New year, blank slate!",
      "Hello!\n\nI have created and shared new documents for 2018.\n\nThanks,\nSole");
  });
}

function getReplacements(data) {
  var replacements = {
    'PERSON': data.name,
    'YYYY': 2018
  };
  return replacements;
}

function personalise(str, data) {
  var out = str;
  var replacements = getReplacements(data);
 
  Object.keys(replacements).forEach(function(term) {
    out = out.replace(term, replacements[term]);
  });
  return out;
}

function personaliseDoc(doc, data) {
  var replacements = getReplacements(data);
  var body = doc.getBody();

  Object.keys(replacements).forEach(function(term) {
    body.replaceText(term, replacements[term]);
  });
}

function clone(docId) {
  var doc = DriveApp.getFileById(docId).makeCopy();
  return doc;
}

To run this, you need to select the main function from the drop down, and press the ‘play’ button to run. You’ll need to authorise the script to read and write your docs, files and email. Scary! 😛

Each template document has a few fields that will be replaced with data when running the script. For example the title in the document is “PERSON:My name 1:1 YYYY”; when run the PERSON and YYYY placeholders will become Person number one and 2018 respectively. If you want to have more fields, you need to add them to the getReplacements function. This replacement is done using a regular expression, so admittedly you could do really fancy things here (or shoot yourself in the feet, heh!).

And the personalise* functions aren’t the most DRY you’ve ever seen, but I figured that they were simple enough and I really didn’t need to refactor them to use iterators and what not. Sometimes simple enough is enough…

Regarding Apps Script, I found the distinction between the document and the file a bit tricky at the beginning. The DriveApp only cares about files and folders, and not whether they’re documents or spreadsheets or whatever. So that’s what’s used in clone(). But then to open the document you need the id of the file clone, and use that with the DocumentApp. But this is my only minor gripe, and I think once you understand, it becomes a non-issue.

The documentation is quite decent, although a few more code samples would be nice. For example, it doesn’t tell you how to send multiline messages; I figured I could use \n because I’m an old dev, but I wonder whether someone who’s not used to ‘low level C’ would have come up with that idea naturally.

I also can’t stop being amazed that they really use a floppy disk as the icon for saving. I wonder if young people know what that is; I haven’t even owned a computer with a floppy disk in more than 10 years!

I haven’t figured out how not to use their online editor, but this script was simple enough that I didn’t need to. If I did more work with App Script, I would try to figure out things such as how to version control, etc.

All in all this is quite enjoyable to use, and once you start looking at the API you wonder what else could you automate… ah, the possibilities!

Looking back at 2017

Another year, another late-ish post looking at the previous year!

But this time I have a very good excuse, as we were away for three weeks on our honeymoon trip (!). So let’s inevitably start with the big personal highlight of the year: we got married! My last year’s wonderful partner is now my wonderful spouse. TA-DA! 🎺

Other personal achievements:

  • Cycled 228 trips, for a total of 1170 km over 73 hours, all in London (for a sense of scale, Barcelona is 1137 km away)
  • I ran way less: ~150km versus the ~400km of 2015, but…
  • … I have a personal trainer now! This 1:1 supervision has helped me in various ways:
    • I understood what working in tech is doing to my body
    • I am also weight training now, so my strength is not all just on my legs (as when I was just running/cycling). I was also curious about this for a long time, and I’m very excited about getting stronger!
  • I also started doing yoga, which has been very fulfilling–not only in the immediate sense of being grounded and calm after a session; but witnessing the progression from “kinda OK” to “not only can I do these complicated bindings but even hold myself in the air for a while, wow” has proved me that practice pays off. Next goal: headstands!
  • I taught myself to swim, front crawl style. Again, it took me many failed attempts but eventually something “just clicked”, and I DID IT! Before, I was terrified about getting my nose under water, thanks to some horrible swimming ‘lessons’ that traumatised me when I was 10. It’s never too late! All it takes is some determination, a quiet, shallow pool in which to fail as many times as you need, and lots of patience with yourself.
  • I kept meditating. This was incredibly useful, specially in various tense situations last year.
  • I took a considerable Twitter break (it’s very good)

Work wise:

  • Moved to work full time in the DevTools team
  • Learned more about Firefox’s and DevTools’ internals (there. is. so. much.)
  • Committed code to DevTools (example)
  • Kept my focus on DevTools, by rejecting all invitations to speak at conferences–this takes a disproportionate amount of time if you want to do it well! (And I want to do things well)
  • Was hiring manager for two positions, and involved in hiring for other positions – my interviewing/hiring skills went up by ten or twenty notches!
  • I’ve finally had the time to start learning React and Redux. I’m still a beginner though, but I can see why people can be so excited about it… and why things can go wrong so fast too! I’m using them to write a tool to manage and query lots of data, client side, so it’s an interesting problem

Things I did for the first time:

  • Visited: Cambridge, Cornwall, Antwerp, Philippines
  • Successfully baked muffins, also my own granola and protein-y bars!
  • Learnt to cook yummy Indian food. An immense amount of dhals have been cooked and consumed this year.
  • Played gamelan instruments (during a workshop at the Southbank Centre)
  • Went to Thorpe Park 😱🎢
  • Went underground and aboard the Mail Rail
  • Visited the London Olympic Stadium (for the Summer athletics)
  • Found where to buy Valencian horchata in my neighbourhood – all my needs are fulfilled now 😂
  • Went all the way to the end of one branches of the Metropolitan line: Amersham! ZONE 9! Spooky 👻 (I went to Watford in 2013)
  • Flew BA001 (the old Concorde flight number!) albeit on a more modern Airbus aircraft (boo!)
  • Didn’t drink a sip of Coke in the whole year, and hardly any fizzy drink at all

Finally, things I did for the first time… and hopefully the last:

  • Had a bike accident involving a pedestrian – she was not harmed (thanks to my braking skills), but I was unable to cycle for a month! HARRUMPH!
  • Dropped a 25lbs weight plate on my foot (gaah)
  • Rode a Segway (not for me)

🎉To 2018! 🍹

Was it `from XYZ import ABC` or was it `import ABC from XYZ`?

I was writing (read: hacking together) some Python code yesterday and when today I came back to the ES6-flavoured JavaScript I’m writing as of late, I immediately got my import declarations wrong!

Instead of

// ES6
import ABC from 'xyz';

… I accidentally typed

// ES-Python?
from 'xyz' import ABC;

… which is more like the Python syntax.

I normally advocate for copying what everyone knows, i.e. ES6 designers should have copied Python, as it makes it easier for developers to pick up new languages or features.

But if you think about that, the Python syntax is pretty awkward and counter to the way English reads, unless you’re exercising some fancy stylistic choices. And code is not the right place for fancy styles, specially if you want others to be able to pick where you left.

So I’m glad ES6 designers did not copy Python in this case. Not so glad that my brain seems to prefer the awkward choice!

Tell me more about this intriguing future

Firefox 1.0 was released on the 9th of November of 2004, and I still remember the buzz. We were all excitedly downloading it because our browser had finally reached v1.0.

Using Firefox at that time, with all the developer extensions, gave you such an advantage over other web developers. Adding in the tabs, and the way it was predictable (CSS standards wise), and that it wouldn’t get infected with stuff as often as Internet Explorer, made it into such a joyous experience.

Now, if you had told me back then that I’d be contributing code to Firefox, I’d be laughing in your face. But then I’d stop and ask: Wait… what? Tell me more about this intriguing future!

Fast forward thirteen years. I am working at Mozilla, and tomorrow we release Firefox Quantum to the general public. It’s, as the name says, a “quantum leap” between this and previous Firefox versions.

I’m personally excited that I’ve contributed code to this release. I worked on removing dependencies on the (now defunct) Add-on SDK from the code base of Developer Tools. This means that the SDK code could be finally removed from Firefox, as the new WebExtensions format that Firefox uses now does not make use of that SDK. Results? Safer and leaner Firefox (the old SDK exposed way too many internals). Oh, and that warm and fuzzy feeling after deleting code…

So I didn’t contribute to a big initiative such as a new rendering engine or whatnot, but it’s often the little non-glamourous things that need to be done. I’m proud of this work (which was also done on time). My team were great!

Another aspect I’m very thrilled about is how this work has set us up for more successes already, as we’ve developed new tools and systems to find out ‘bad stuff’ in our code, and now we’re using these outside of the Firefox “core” team to identify more things we’ll want to improve in the upcoming months. There’s a momentum here!

Who knows what else will the future bring? Maybe in 10 years time I’ll be telling you I shipped code for the new rendering engine in Firefox indeed! One has to be open to the possibilities…

Update: my colleague Lin has explained how Firefox Quantum is a browser for the future, using modern technology.

No Twitter month

As an experiment, I stopped using Twitter in October (I quietly posted daily updates on a page)

If it were considered an addiction, it took me a month to cure myself from it (although I had already taken steps to use it less).

After the experiment was over, I didn’t rush to Twitter. I just went on with my life and didn’t even give it a thought. I haven’t been to Twitter yet, and we’re 10 days into November.

I thought I would post more to my blog instead, but I actually have been so busy I barely posted anything, so I can’t tell if the levels of activity (views, comments) have been affected by not posting links to my blog in twitter, because I have barely blogged.

I might continue the no-twitter experiment for longer while also posting more here, and observe what happens.

I keep wondering if it would be “good” for my reach to re-enable the plugin that posts to twitter from my blog automatically, but it still makes me extremely queasy to share a platform with the kind of detestable, repulsive content that Twitter allows to exist because it makes them money (even if it goes against their own rules).