> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Surfacing activities with GitHub actions

> Learn how to leverage the predictable link in your workflow by integrating Platform.sh with GitHub Actions. Streamline your deployments and error handling.

export const PostMeta = ({data = {}}) => {
  const {author, date} = data;
  const authors = Array.isArray(author) ? author : author ? [author] : [];
  const resolveAuthor = slug => {
    const entry = AUTHOR_MAP[slug] || ({});
    const name = entry.name || slug;
    const github = entry.github || null;
    const linkedin = entry.linkedin || null;
    const url = github ? `https://github.com/${github}` : linkedin || null;
    const avatarUrl = github ? `https://github.com/${github}.png?size=64` : null;
    return {
      name,
      url,
      avatarUrl
    };
  };
  const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }) : null;
  if (authors.length === 0 && !formattedDate) return null;
  const AUTHOR_MAP = {
    "aaron-collier": {
      "name": "Aaron Collier"
    },
    "aaron-dudenhofer": {
      "name": "Aaron Dudenhofer"
    },
    "aaron-porter": {
      "name": "Aaron Porter"
    },
    "adriaan-odendaal": {
      "name": "Adriaan Odendaal"
    },
    "ajmal": {
      "name": "Ajmal Siddiqui"
    },
    "akalipetis": {
      "name": "Antonis Kalipetis"
    },
    "alexander-varwijk": {
      "name": "Alexander Varwijk"
    },
    "alicia-bevilacqua": {
      "name": "Alicia Bevilacqua"
    },
    "amelie-deguerry": {
      "name": "Amelie Deguerry"
    },
    "anacidre": {
      "name": "Ana Cidre",
      "linkedin": "https://www.linkedin.com/in/ana-cidre"
    },
    "andoni": {
      "name": "Andoni Auzmendi"
    },
    "andrei-taranu": {
      "name": "Andrei (Alex) Taranu",
      "linkedin": "https://www.linkedin.com/in/andrei-alex-taranu/"
    },
    "andrew-baxter": {
      "name": "Andrew Baxter"
    },
    "andrew-melck": {
      "name": "Andrew Melck"
    },
    "antoine-crochet-damais": {
      "name": "Antoine Crochet Damais"
    },
    "augustin-delaporte": {
      "name": "Augustin Delaporte",
      "linkedin": "https://www.linkedin.com/in/augustindelaporte/"
    },
    "branislav-bujisic": {
      "name": "Branislav Bujisic"
    },
    "carl-smith": {
      "name": "Carl Smith"
    },
    "caroline-leroy": {
      "name": "Caroline Leroy"
    },
    "cati-mayer": {
      "name": "Cati Mayer"
    },
    "catplat": {
      "name": "C Trinkwon"
    },
    "ceelolulu": {
      "name": "Celeste van der Watt"
    },
    "chadwcarlson": {
      "name": "Chad Carlson",
      "github": "chadwcarlson",
      "linkedin": "https://www.linkedin.com/in/chadwcarlson"
    },
    "chris-ward": {
      "name": "Chris Ward"
    },
    "chris-yates": {
      "name": "Chris Yates"
    },
    "christian-sieber": {
      "name": "Christian Sieber"
    },
    "christopher-lockheardt": {
      "name": "Christopher Lockheardt"
    },
    "christopher-skene": {
      "name": "Christopher Skene"
    },
    "chuck-morgan": {
      "name": "Chuck Morgan"
    },
    "corey-dockendorf": {
      "name": "Corey Dockendorf"
    },
    "crell": {
      "name": "Crell"
    },
    "damz": {
      "name": "Damz"
    },
    "dan-morrison": {
      "name": "Dan Morrison"
    },
    "davidbonachera": {
      "name": "David Bonachera",
      "github": "davidbonachera",
      "linkedin": "https://www.linkedin.com/in/davidbonachera"
    },
    "dereliahmet1": {
      "name": "Ahmet Faruk Dereli"
    },
    "devicezero": {
      "name": "Jonas Kröger",
      "github": "devicezero",
      "linkedin": "https://www.linkedin.com/in/jonaskroeger/"
    },
    "doug-goldberg": {
      "name": "Doug Goldberg"
    },
    "duncan-naves": {
      "name": "Duncan Naves",
      "github": "duncannaves",
      "linkedin": "https://www.linkedin.com/in/duncan-naves-a94423aa"
    },
    "erika-bustamante": {
      "name": "Erika Bustamante"
    },
    "fabpot": {
      "name": "Fabien Potencier"
    },
    "flovntp": {
      "name": "Florent Huck",
      "github": "flovntp",
      "linkedin": "https://www.linkedin.com/in/florenthuck"
    },
    "fred-plais": {
      "name": "Fred Plais"
    },
    "gauthier-garnier": {
      "name": "Gauthier Garnier"
    },
    "gilzow": {
      "name": "Paul Gilzow"
    },
    "gmoigneu": {
      "name": "Guillaume Moigneu",
      "github": "gmoigneu",
      "linkedin": "https://www.linkedin.com/in/guillaumemoigneu/"
    },
    "gregqualls": {
      "name": "Greg Qualls"
    },
    "guguss": {
      "name": "Augustin Delaporte"
    },
    "haylee-millar": {
      "name": "Haylee Millar"
    },
    "ivana-kotur": {
      "name": "Ivana Kotur"
    },
    "jackrabbithanna": {
      "name": "Mark Hanna"
    },
    "jared-wright": {
      "name": "Jared Wright",
      "github": "jww-sh",
      "linkedin": "https://www.linkedin.com/in/jaredwaynewright"
    },
    "jessica-orozco": {
      "name": "Jessica Orozco"
    },
    "joey-stanford": {
      "name": "Joey Stanford"
    },
    "john-grubb": {
      "name": "John Grubb"
    },
    "jonas-kruger": {
      "name": "Jonas Kruger"
    },
    "kathryn-frazer": {
      "name": "Kathryn Frazer"
    },
    "kemiojo": {
      "name": "Kemi Elizabeth Ojogbede"
    },
    "kieronsambrook-smith": {
      "name": "Kieronsambrook Smith"
    },
    "laurent-arnoud": {
      "name": "Laurent Arnoud",
      "linkedin": "https://www.linkedin.com/in/laurent-arnoud-861b44121/"
    },
    "letoya-boyne": {
      "name": "Letoya Boyne"
    },
    "lolautruche": {
      "name": "Jérôme Vieilledent"
    },
    "lyly-lepinay": {
      "name": "Lyly Lepinay"
    },
    "manauwar-alam": {
      "name": "Manauwar Alam"
    },
    "marc-antoine-porri": {
      "name": "Marc Antoine Porri"
    },
    "maria-antinkaapo": {
      "name": "Maria Antinkaapo"
    },
    "maria-de-anton": {
      "name": "Maria De Anton"
    },
    "mark-dorison": {
      "name": "Mark Dorison"
    },
    "markus-hausammann": {
      "name": "Markus Hausammann"
    },
    "mary-thomas": {
      "name": "Mary Thomas"
    },
    "mathias-bolt-lesniak": {
      "name": "Mathias Bolt Lesniak"
    },
    "mathieu-strauch": {
      "name": "Mathieu Strauch"
    },
    "matthias-van-woensel": {
      "name": "Matthias Van Woensel",
      "linkedin": "https://www.linkedin.com/in/matthias-van-woensel-267a069"
    },
    "maz-mohammadi": {
      "name": "Maz Mohammadi"
    },
    "michael-sharp": {
      "name": "Michael Sharp"
    },
    "mupsi": {
      "name": "Marine Gandy"
    },
    "natalie-harper": {
      "name": "Natalie Harper"
    },
    "ngommenginger": {
      "name": "Nicolas Gommenginger",
      "linkedin": "https://www.linkedin.com/in/nicolas-gommenginger"
    },
    "nicholas-bennison": {
      "name": "Nicholas Bennison"
    },
    "nicholas-vahalik": {
      "name": "Nicholas Vahalik"
    },
    "nick-hardiman": {
      "name": "Nick Hardiman"
    },
    "nickanderegg": {
      "name": "Nickanderegg"
    },
    "nicolas-grekas": {
      "name": "Nicolas Grekas",
      "github": "nicolas-grekas",
      "linkedin": "https://www.linkedin.com/in/nicolasgrekas/"
    },
    "niti-malwade": {
      "name": "Niti Malwade"
    },
    "opensocialteam": {
      "name": "Opensocialteam"
    },
    "ori-pekelman": {
      "name": "Ori Pekelman"
    },
    "otavio-santana": {
      "name": "Otavio Santana"
    },
    "palwandi": {
      "name": "Pawan Alwandi",
      "github": "pawpy",
      "linkedin": "https://www.linkedin.com/in/pawanalwandi"
    },
    "patrick-boest": {
      "name": "Patrick Boest"
    },
    "patrick-dawkins": {
      "name": "Patrick Dawkins",
      "github": "pjcdawkins",
      "linkedin": "https://www.linkedin.com/in/patrickdawkins"
    },
    "patrick-klima": {
      "name": "Patrick Klima"
    },
    "pjcdawkins": {
      "name": "Pjcdawkins"
    },
    "prineet-kaurbhurji": {
      "name": "Prineet Kaurbhurji"
    },
    "quentin-sinig": {
      "name": "Quentin Sinig"
    },
    "ralt": {
      "name": "Florian Margaine",
      "github": "ralt",
      "linkedin": "https://www.linkedin.com/in/florian-margaine-43971136"
    },
    "ramanathanramakrishnamurthy": {
      "name": "Ramanathanramakrishnamurthy"
    },
    "remi-lejeune": {
      "name": "Rémi Lejeune"
    },
    "ribel": {
      "name": "Taras Kruts"
    },
    "robert-douglass": {
      "name": "Robert Douglass"
    },
    "rudy-weber": {
      "name": "Rudy Weber"
    },
    "ryan-hicks": {
      "name": "Ryan Hicks"
    },
    "sabri-helal": {
      "name": "Sabri Helal"
    },
    "savannah-bergeron": {
      "name": "Savannah Bergeron"
    },
    "shannon-vettes": {
      "name": "Shannon Vettes"
    },
    "shawn-ogasawara": {
      "name": "Shawn Ogasawara",
      "linkedin": "https://www.linkedin.com/in/shawn-ogasawara-83a9a0/"
    },
    "shawna-spoor": {
      "name": "Shawna Spoor"
    },
    "shedrack-akintayo": {
      "name": "Shedrack Akintayo"
    },
    "simon-ruggier": {
      "name": "Simon Ruggier"
    },
    "sophie-van-der-kindere": {
      "name": "Sophie Van Der Kindere"
    },
    "stefanos-thampis": {
      "name": "Stefanos Thampis"
    },
    "stephen-weinberg": {
      "name": "Stephen Weinberg"
    },
    "sukhman-virk": {
      "name": "Sukhman Virk"
    },
    "sumaira-nazir": {
      "name": "Sumaira Nazir"
    },
    "sumer": {
      "name": "Sümer Cip"
    },
    "syed-raza": {
      "name": "Syed Raza"
    },
    "tamara-bacchia": {
      "name": "Tamara Bacchia"
    },
    "tara-arnold": {
      "name": "Tara Arnold"
    },
    "theosakamg": {
      "name": "Mickael Gaillard",
      "github": "theosakamg"
    },
    "thomasdiluccio": {
      "name": "Thomas di Luccio"
    },
    "tim-anderson": {
      "name": "Tim Anderson"
    },
    "tom-helmer-hansen": {
      "name": "Tom Helmer Hansen"
    },
    "tylermills": {
      "name": "Tyler Mills"
    },
    "upsun": {
      "name": "Upsun"
    },
    "veronika-tolkachova": {
      "name": "Veronika Tolkachova",
      "linkedin": "https://www.linkedin.com/in/veronika-tolkachova-169167a2"
    },
    "vince-parker": {
      "name": "Vince Parker"
    },
    "vinnie-russo": {
      "name": "Vincenzo Russo"
    },
    "vrobert78": {
      "name": "Vincent Robert",
      "github": "vrobert78",
      "linkedin": "https://www.linkedin.com/in/vincent-robert-498a883"
    },
    "yuriy-babenko": {
      "name": "Yuriy Babenko"
    },
    "yuriy-gerasimov": {
      "name": "Yuriy Gerasimov"
    }
  };
  return <div className="post-meta">
      {(authors.length > 0 || formattedDate) && <div className="post-meta-info">
          {authors.length > 0 && <div className="post-meta-authors">
              {authors.map(slug => {
    const {name, url, avatarUrl} = resolveAuthor(slug);
    const inner = <>
                    {avatarUrl && <img src={avatarUrl} alt={name} className="post-meta-avatar" />}
                    <span className="post-meta-author-name">{name}</span>
                  </>;
    return url ? <a key={slug} href={url} target="_blank" rel="noopener noreferrer" className="post-meta-author">
                    {inner}
                  </a> : <span key={slug} className="post-meta-author">{inner}</span>;
  })}
            </div>}
          {authors.length > 0 && formattedDate && <span className="post-meta-separator" aria-hidden="true">·</span>}
          {formattedDate && <span className="post-meta-date">{formattedDate}</span>}
        </div>}
    </div>;
};

<PostMeta data={{ author: ["chadwcarlson"], date: "2021-12-22", image: "/images/posts/unknown/share-your-activities-with-robots-surfacing-activities-with-github-actions/cover.webp" }} />

In our [previous announcement](https://platform.sh/blog/share-your-failures-fix-them-faster/), we announced that activities on Platform.sh
were now shareable. If you need to send a member of our support team something fishy going on in your build hook, or get
help from another member of your team about a database migration taking longer than expected, you can send them to the
exact logline you need help with.

But like I said before, having the ability to link to a line of logs - if the format of that link is consistent (which
it is) - means we can surface it wherever and whenever it’s useful.

Let’s take a minute to look at the link we can copy for an activity:

```
https://console.platform.sh/USER-ORG/PROJECT-ID/ENVIRONMENT/log/ACTIVITY-ID
```

So how can we leverage this new predictable link in our workflow? The first thing that comes to my mind is surfacing
this link in my GitHub integrations.

Say I have a [repository on GitHub](https://docs.platform.sh/development/private-repository.html), that’s been
integrated to a Platform.sh project. My default branch is my production environment, and I’ve set up the standard
integration defaults otherwise: environments are created automatically for pull requests. All of my team’s review takes
place on these pull requests, and our checks on GitHub fail should the deployment for environment `pr-X` fail.

When they fail, we have the same round-a-bout investigation path I described in the previous post to diagnose where
things broke: go to the project, go to the environment, find the activity, view the log, find the line where things went
wrong.

But since I can share a log for an activity directly, and since I know what the structure of that link is going to look
like, I should be able to remove that added work by quite a bit. Here’s what I want:

* A pull request on GitHub provisions a development environment on Platform.sh.
* Each commit on the pull request results in a commit on that development environment.
* I need successful deployments to be a deciding factor on whether or not that PR is successful, not merging if the
  deployment fails.
* I want to know whether a deployment has failed.
* If a deployment has failed, I’d like the shareable activity link to be surfaced directly on GitHub where my team is
  reviewing, so that they can quickly investigate what happened.

The first two pieces are taken care of by our GitHub integration already, and the third item is something you’re likely
already enforcing with your repository’s settings. The last two is what we’ll focus on here, and that’ll mean writing
some tools that respond to deployments to trigger some other workflow that can surface our activity link when it fails.

Whatever method we choose, to “build” the activity link we need to be able to retrieve the following from each
deployment:

* That and when an environment’s deployment has failed
* The user/organization that the project belongs to
* The project’s ID
* The environment ID
* The failed activity ID

Let’s get into it.

### Waiting to deploy: Environment status via GitHub actions

If you’re not already familiar with [GitHub actions](https://github.com/features/actions), they are a method for
including some configuration in [YAML](https://docs.platform.sh/overview/yaml/what-is-yaml.html) that can be used to
trigger tests and other workflow steps in
[response to events](https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows) that take
place on your repository.

You can, for example, ensure that every commit pushed to your Python library can be used on
[a number of Python versions](https://github.com/platformsh/config-reader-python/blob/master/.github/workflows/quality-assurance.yaml)
your users may have on their computers, or you can automatically
[publish your Node.js library to npm](https://github.com/platformsh/config-reader-nodejs/blob/master/.github/workflows/npm-publish.yaml)
each time a new tag is pushed to the default branch.

Platform.sh connects pushed commits to deployments, usually within the scope of a pull request, and the GitHub
integration will add a
[status check](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks)
to your pull requests that reflect the status of that deployment.

Each time you push to a pull request on GitHub that has been integrated with Platform.sh, a webhook is triggered to let
Platform.sh know that it should sync with the repository and deploy the new commit. After this has occurred, Platform.sh
will respond back to GitHub the environment’s status, which becomes available at `repos/:repos/status/:ref`. It will
begin with a status of `pending` as the environment deploys, but once finished another message from Platform.sh will
update GitHub of the successful deployment:

```json theme={null}
{
  "state": "success",
  "statuses": [
    {
       ...
      "state": "success",
      "description": "Platform.sh: Environment deployed",
      "target_url": "http://pr-35-qsr3ccy-o3fc5w4n5iumy.eu-3.platformsh.site/",
      "context": "platformsh",
    }
  ],
…
}
```

The environment has deployed successfully, and Platform.sh sends along the generated environment URL in the `target_url`
attribute - which allows you to click the **Details** link on the Platform.sh check on a pull request and see the
deployed environment.

<img src="https://mintcdn.com/upsun-c9761871/GmjO2Q50fc4SCexK/images/posts/unknown/share-your-activities-with-robots-surfacing-activities-with-github-actions/gh-success.webp?fit=max&auto=format&n=GmjO2Q50fc4SCexK&q=85&s=a93d4cd7ebc060ee1d37fbd85317cfdf" alt="Success GitHub Status" width="1373" height="416" data-path="images/posts/unknown/share-your-activities-with-robots-surfacing-activities-with-github-actions/gh-success.webp" />

If the environment fails to deploy, however, it will look more like this:

```json theme={null}
{
  "state": "failure",
  "statuses": [
    {
       ...
      "state": "failure",
      "description": "Platform.sh: Environment deployment failed",
      "target_url": "http://pr-35-qsr3ccy-o3fc5w4n5iumy.eu-3.platformsh.site/",
      "context": "platformsh",
    }
  ],
…
}
```

Exactly the same, just with a different `state` message.

<img src="https://mintcdn.com/upsun-c9761871/GmjO2Q50fc4SCexK/images/posts/unknown/share-your-activities-with-robots-surfacing-activities-with-github-actions/gh-failure.webp?fit=max&auto=format&n=GmjO2Q50fc4SCexK&q=85&s=ce696ca78fe632819956d38205c0b266" alt="Failure GitHub Status" width="1373" height="416" data-path="images/posts/unknown/share-your-activities-with-robots-surfacing-activities-with-github-actions/gh-failure.webp" />

The target URL isn’t updated based on the failed deployment, so our **Details** link will simply take us to the
environment URL which has failed to deploy. The failed URL doesn't help us much here, but it does seem like the
opportune place to insert our activity link. So, from here we have a clear goal: detect the failed deployment and update
the `target_url` attribute with our failed activity link.

We start by creating some space for our
[workflows](https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions)

```bash {filename="Terminal"} theme={null}
mkdir .github
mkdir .github/workflows
touch .github/workflows/post-deploy.yaml
```

In a `.github/workflows/post-deploy.yaml` file we can start building out the action. In the first lines, we define when
this action should occur. For now, we can simply say that it should run on every push to all branches except our default
branch - we’ll add some logic later on to make sure this only runs on pull requests. After that, we can expose a CLI
token and a GitHub token as environment variables to be used by the action.

```yaml {filename=".github/workflows/post-deploy.yaml"} theme={null}
---
name: Post-deploy

on:
  push:
    branches-ignore:
      - "main"

env:
  PLATFORMSH_CLI_TOKEN: ${{ secrets.CLI_TOKEN }}
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

> You can add your Platform.sh CLI token as a repository or organization level
> [secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets).

Now it’s time to define a few jobs. The first one is simply meant to determine:

* Are we working from a pull request?
* What is the URL (`target_url`) of the environment associated with the pull request?
* What’s the status of the deployment?

Below is the snippet for the job `build` we’ll use to do this.

```yaml {filename=".github/workflows/post-deploy.yaml"} theme={null}
jobs:
  build:
    runs-on: ubuntu-latest
    name: "Get environment URL"
    outputs:
      commit_status: ${{ steps.status.outputs.env_status }}
      env_url: ${{ steps.url.outputs.env_url }}
      integration_status: ${{ steps.wait.outputs.integration }}
    steps:
      - uses: actions/checkout@v2
      - name: "Await deployment"
        id: wait
        run: |
          COMMIT_STATUS="pending"
          sleep 10
          STATUSES=$(curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA  | jq -r 'length')
          if [ $STATUSES == 0 ]; then
              echo "Not on a Platform.sh integrated environment. Skipping."
              echo "::set-output name=integration::none"
          else
              until [ "$COMMIT_STATUS" == "success" ] || [ "$COMMIT_STATUS" == "failure" ]; do
                sleep 30
                ENV_URL=$(curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA  | jq -r '.[0].target_url')
                COMMIT_STATUS=$(curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA  | jq -r '.[0].state')
                
                echo "Waiting for Platform.sh environment to deploy ...."
                echo "  - $GITHUB_SHA"
                echo "  - $COMMIT_STATUS"
                echo "  - $ENV_URL"
              done
              echo "Environment deployed. Finished."
              echo "::set-output name=integration::platformsh"
          fi
      - name: "Pass status"
        id: status
        if: steps.wait.outputs.integration == 'platformsh'
        run: |
          COMMIT_STATUS=$(curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA  | jq -r '.[0].state')
          echo "::set-output name=env_status::$COMMIT_STATUS"
      - name: "Pass URL"
        id: url
        if: steps.wait.outputs.integration == 'platformsh'
        run: |
          ENV_URL=$(curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA  | jq -r '.[0].target_url')
          echo "::set-output name=env_url::$ENV_URL"
```

In summary, we are:

* Placing a request on the Status API.
* Verifying that this is associated with an environment on Platform.sh (deploying from a Pull request).
* Waiting for the status to update from `pending` to `success` or `failure`. In the case above, we’re literally placing
  requests on the status and waiting for it to change. This is unlikely to be the most efficient way of doing this, but
  it works - so ship it.
* Passing that status and `target_url` value out of job `build`, so that those values will be accessible from subsequent
  jobs.

Now that the environment has deployed, we can pipe in these outputs to build the activity link in a new job called
`displaylogs`:

```yaml {filename=".github/workflows/post-deploy.yaml"} theme={null}
displaylogs:
  name: "Display logs on failed deploy"
  runs-on: ubuntu-latest
  needs: build
  if: needs.build.outputs.commit_status == 'failure'
  steps:
    - name: "Install the Platform.sh CLI"
      run: |
        # Install Platform.sh CLI
        curl -sS https://platform.sh/cli/installer | php
    - name: "Retrieve the logs"
      id: activity
      run: |
        # Get data.
        IFS='-' read -ra my_array <<< "${{ needs.build.outputs.env_url }}"
        ENVIRONMENT="pr-${my_array[1]}"
        IFS="." read -ra my_array <<< "${my_array[3]}"
        PROJECT="${my_array[0]}"
        ACTIVITY=$(~/.platformsh/bin/platform project:curl -p $PROJECT /environments/$ENVIRONMENT/activities | jq -r '.[0].id')

        LOG_URL=https://console.platform.sh/projects/$PROJECT/$ENVIRONMENT/log/$ACTIVITY
        echo "      * PROJECT:     $PROJECT"
        echo "      * ENVIRONMENT: $ENVIRONMENT"
        echo "      * ACTIVITY:    $ACTIVITY"
        echo "      * LOGS:        https://console.platform.sh/projects/$PROJECT/$ENVIRONMENT/log/$ACTIVITY"
        echo "::set-output name=log_url::$LOG_URL"
    - name: "Update integration target_url"
      run: |
        curl -s --location --request POST --header "Authorization: Bearer $GITHUB_TOKEN" \
            --header 'Content-Type: application/json' \
            --data-raw '{"state": "failure", "target_url": "${{ steps.activity.outputs.log_url }}", "context": "platformsh", "description": "Platform.sh: Failed to deploy environment. View the activity log details."}' \
            https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA
```

You’ll notice that job `displaylogs` `needs` the job `build` - it will only run after `build` has completed, and it will
be able to access its outputs from the namespace `needs.build.outputs.X`. It’s from this that we can restrict
`displaylogs` to make our update to only those cases when the environment deployment has failed. After this check, the
job

* Installs the Platform.sh CLI.
* Uses the `target_url` to retrieve our project ID, and environment name for the pull request.
* Places a request on the `projects/environments/:environment/activities` endpoint on the Platform.sh API to get the
  most recent failed activity (and its ID).
* Builds the activity log URL from the project ID, environment ID, and activity ID at
  [console.platform.sh](https://console.platform.sh).
* Places a final request onto the GitHub Status API, which updates the `target_url` value from Platform.sh to our
  activity URL.

With these changes in place, any failed deployment pushed to GitHub will be updated in this way. A successful deployment
will provide the environment’s URL in the **Details** link of the integration status as usual, but anything that fails
will take our team directly to the failed logs so that we can investigate what went wrong.

You can find the Gist of the GitHub action described above
[here](https://gist.github.com/chadwcarlson/96d6c4cb8c6b371ab04e256554dde728/1a608fd4fff4042aa2116a32bb925b482a9a2100).

Let us know where you’d like to see shareable activities by
[joining the Platform.sh Slack](https://chat.platform.sh), and share with us how you’ve used it in your own workflow on
our [Community](https://community.platform.sh).
