A few weeks ago, when COVID-19 started to hit us, everything moved to the remote world. Some people started to share ideas or helpers for remote meetings, improve communication, and get a more focused conversation.

One of those ideas was a virtual deck of cards you could load in your mobile phone (through a web page), put it full screen, and communicate something to the group.

As an example, you could check a live version here: Cards used by DTI based on this project from Stephen Walker.

I wanted to find a way where anybody could change the cards with no coding knowledge or access to remote folders or servers

Said someone once

That's how this small experiment started. After thinking about the options for a while, I ended up with this:

  • I'm going to use a Google Slides presentation as the "creation tool" for the cards.
  • Each card of the deck is going to be one slide in the presentation.
  • There will be some kind of trigger to know that the cards are ready and launch the update process.
  • Get the presentation through Google Drive APIs.
  • Slice every slide and convert them to a png file.
  • Put the png files where the website could read them.

Google Slides, the source of truth for non-programmers

Google Slides is simple, everybody knows how to create slides in a PowerPoint-like tool so we started here.

This is how it looks like:

Google Slides Example

Atlassian Jira, the trigger

The latest versions of Jira Cloud (not sure Server version yet) have a nice included add-on to generate automation based on triggers and actions. One of them, lets you define something like you see in the next screen:

Jira Automation Screenshot

To summarize, it works like this:

  • It's triggered when any user creates an issue in this project.
  • Checks if the issue type is "Deploy Cards", that I previously defined as an issue type for this project.
  • If the issue type matches, it sends a post request to some endpoint I defined there. I don't care too much about sending data to this endpoint because the only thing I need is the signal that I could start deploying but if you need something else for your experiments, you could send all sorts of data from there (Jira related data)
  • The next step moves the issue to the "Test" status, just to inform the user that we did what she wanted.
  • Finally, adds a comment in the issue to make it easy for them to find the final URL with the results.

AWS S3, the image repository

I didn't want to complicate myself running a "real" deployment in a server just to update images so my quick workaround was to point the HTML that looks for the images to a public bucket in AWS. In this way, whenever I want to export the images, I just get them from the Google Slides, generate the PNGs, and copy them to the right bucket.

Let's take a look at how the code works for those actions.

The code

If you are using Symfony, you'll probably find some issues. The first blocker I've found, it was being able to use the Google Drive SDK library for PHP inside the framework. I've tested it in Symfony 5 and took me a while to find my way to load the credentials needed to make the library works correctly. The thing is, you have a credential's file where you store the connection data (please, outside your root directory).

For the library to load the file, you need to specify the path. If you put the info in your .env file, it works until you run the "composer.phar dump-env prod". As soon as you do that, Symfony starts using the local cache generated, and the library stops finding the environment variable.

This is because of the way Symfony handles the use or not of the local dump. If the dump exists, the variables are not loaded in the ENV array, which makes things much more complicated. So, what I did, I don't use the dump, just use a deployment generated .env.local file.

The next step, now that we know how to find the file, was to load the JSON file and use the credentials. For that purpose, I found a nice Symfony "magic" to be able to insert the configuration directly into a service. This is an extract from my services.yaml file:

GoogleClient:
    class: Google_Client
    arguments:                                    
        - "%env(json:file:GOOGLE_APPLICATION_CREDENTIALS)%"       

Symfony has several useful Environment Variable Processors that you can pipe to fulfill your needs. In this case:

  • Find the path stored in GOOGLE_APPLICATION_CREDENTIALS environment variable.
  • Read the file.
  • Get it into the service as an array coming from a JSON encoded string.

Just like that 🤷‍♂️

The rest of the code is pretty self-explanatory. A method I've defined to find the file/s I need from Google Drive.

  • Connect to the Drive service
  • Send a query to get files matching the definition
  • Return those files
/**
 * @param $filename
 * @return Google_Service_Drive_DriveFile|array
 */
protected function getFileDataFromGDrive($filename)
{
    $service = new Google_Service_Drive($this->client);
    $params  = [
        'q'      => "name='".$filename."'",
        'spaces' => 'drive',
    ];
    $result  = $service->files->listFiles($params);

    return $result->getFiles();
}
    

This method is used in the main part of the controller. This is the endpoint Jira calls whenever there is a new deployment task created:

/**
 * @Route("/api/someniceEndpointNameHere", name="api_detect_changes")
 * @param Request $request
 * @return Response
 */
public function defaultAction(Request $request): Response
{
    $files = $this->getFileDataFromGDrive('Name of the doc I want to find');
    /** @var Google_Service_Drive_DriveFile $file */
    $file = array_shift($files);

    $slidesService = new Google_Service_Slides($this->client);
    $slides        = $slidesService->presentations->get($file->getId())->getSlides();
    $numImages     = 0;
    
    foreach ($slides as $key => $slide) {
        $slide_num_for_humans = $key + 1;
        $thumbnail = $slidesService->presentations_pages->getThumbnail(
            $file->getId(),
            $slide->getObjectId(),
            [
                'thumbnailProperties.mimeType'      => 'PNG',
                'thumbnailProperties.thumbnailSize' => 'LARGE',
            ]
        );
        $this->uploader->uploadFile(
            'mtg-crd-'.sprintf('%02d', $slide_num_for_humans).'.png',
            file_get_contents($thumbnail->contentUrl)
        );
        $numImages++;
    }

    $response = JsonResponse::fromJsonString('{ "status": "ok", "images": '.$numImages.' }');
    $response->headers->set('Access-Control-Allow-Origin', '*');

    return $response;
}

So, what is happening here?

  • Receive the call from Jira and executes the method.
  • Get the file I need from Google Drive (the Google Slide document I've shown above). Be careful, I use an array_shift to get the first element of the array of documents I receive from Google Drive. This is error-prone and I do it because for the experiment I KNOW there is going to be one document, and only one. If it's not your case you should handle it differently.
  • Ask the Google Slide Api for the document, using the getId from the file, and getting the slides from there.
  • Looping through each slide and generating a PNG file using "$slidesService->presentations_pages->getThumbnail"
  • Uploading the generated image to a bucket in S3. What you see as "$this->uploader", is really an interface for an uploader implementation. In this case is AWS SDK for PHP, using the S3 API to put the file there.
  • Finally, I send back some information in case the calling endpoint could make use of it.

It's just a small experiment, but it helped me see that there is potential in a "low-code" implementation of some actions involving synchronization between the "product" and the "tech" part. In other environments, this simple task will require creating a Jira task to someone, asking for some dev to have time to get those images from somewhere, convert them to the right format, upload them to someplace, etc.

With these lines of code, the customer could make all of that happening on-demand, automatically and with no waiting lines, which I think is the best customer experience you could provide.

Hope it creates some thoughts, and you can come up with new and brilliant ideas based on this.

Thanks for reading!