Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile.openapi_decorator
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ COPY --chown=node:node package-lock.json /openapi-check
ADD --chown=node:node script /openapi-check/script
ADD --chown=node:node lib /openapi-check/lib
ADD --chown=node:node content /openapi-check/content
ADD --chown=node:node data /openapi-check/data

RUN npm ci -D

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,15 @@ on:

{% data reusables.webhooks.workflow_run_desc %}

{% data reusables.github-actions.branch-requirement %}
{% note %}

**Notes:**

* This event will only trigger a workflow run if the workflow file is on the default branch.

* You can't use `workflow_run` to chain together more than three levels of workflows. For example, if you attempt to trigger five workflows (named `B` to `F`) to run sequentially after an initial workflow `A` has run (that is: `A` → `B` → `C` → `D` → `E` → `F`), workflows `E` and `F` will not be run.

{% endnote %}

| Webhook event payload | Activity types | `GITHUB_SHA` | `GITHUB_REF` |
| --------------------- | -------------- | ------------ | -------------|
Expand Down
49 changes: 14 additions & 35 deletions lib/hydro.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,10 @@ import fetch from 'node-fetch'
import statsd from '../lib/statsd.js'
import FailBot from '../lib/failbot.js'

const SCHEMAS = {
page: 'docs.v0.PageEvent',
exit: 'docs.v0.ExitEvent',
link: 'docs.v0.LinkEvent',
search: 'docs.v0.SearchEvent',
searchResult: 'docs.v0.SearchResultEvent',
navigate: 'docs.v0.NavigateEvent',
survey: 'docs.v0.SurveyEvent',
experiment: 'docs.v0.ExperimentEvent',
redirect: 'docs.v0.RedirectEvent',
clipboard: 'docs.v0.ClipboardEvent',
print: 'docs.v0.PrintEvent',
preference: 'docs.v0.PreferenceEvent',
}

export default class Hydro {
constructor({ secret, endpoint } = {}) {
this.secret = secret || process.env.HYDRO_SECRET
this.endpoint = endpoint || process.env.HYDRO_ENDPOINT
this.schemas = SCHEMAS
}

/**
Expand All @@ -47,20 +31,14 @@ export default class Hydro {
* @param {any} value
*/
async publish(schema, value) {
return this.publishMany([{ schema, value }])
}

/**
* Publish multiple events to Hydro
* @param {[{ schema: string, value: any }]} events
*/
async publishMany(events) {
const body = JSON.stringify({
events: events.map(({ schema, value }) => ({
schema,
value: JSON.stringify(value), // We must double-encode the value property
cluster: 'potomac', // We only have ability to publish externally to potomac cluster
})),
events: [
{
schema,
value: JSON.stringify(value), // We must double-encode the value property
cluster: 'potomac', // We only have ability to publish externally to potomac cluster
},
],
})
const token = this.generatePayloadHmac(body)

Expand All @@ -81,23 +59,24 @@ export default class Hydro {
statsd.increment(`hydro.response_code.${res.status}`, 1, statTags)
statsd.increment('hydro.response_code.all', 1, statTags)

// Track hydro exceptions in Sentry, but don't track 503s because we can't do anything about service availability
if (!res.ok && res.status !== 503) {
// Track hydro exceptions in Sentry,
// but don't track 5xx because we can't do anything about service availability
if (!res.ok && res.status < 500) {
const err = new Error(`Hydro request failed: ${res.statusText}`)
err.status = res.status

const failures = await res.text()

FailBot.report(err, {
hydroStatus: res.status,
hydroText: res.statusText,
hydroFailures: failures,
})

// If the Hydro request failed as an "Unprocessable Entity", log it for diagnostics
if (res.status === 422) {
const failures = await res.json()
console.error(
`Hydro schema validation failed:\n - Request: ${body}\n - Failures: ${JSON.stringify(
failures
)}`
`Hydro schema validation failed:\n - Request: ${body}\n - Failures: ${failures}`
)
}

Expand Down
13 changes: 12 additions & 1 deletion lib/redirects/static/redirect-exceptions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,15 @@
- /billing/managing-billing-for-your-github-account/managing-invoices-for-your-enterprise

/enterprise-cloud@latest/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization
- /organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization
- /organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization

# The initializing-github-ae.md article is referenced in these code files:
# - lib/github/private_instance_bootstrapper/internal_support_contact.rb
# - lib/github/private_instance_bootstrapper/saml_idp_configuration.rb
# - lib/github/private_instance_bootstrapper/policies_configuration.rb
# This redirect ensures that the links don't resolve to the non-GHAE version
# of the docs as this article only exists in the GHAE docs.

/github-ae@latest/admin/configuration/configuring-your-enterprise/initializing-github-ae
- /admin/configuration/configuring-your-enterprise/initializing-github-ae
- /enterprise-server@latest/admin/configuration/configuring-your-enterprise/initializing-github-ae
17 changes: 16 additions & 1 deletion lib/schema-event.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ const preferenceSchema = {
},
}

export default {
export const eventSchema = {
oneOf: [
pageSchema,
exitSchema,
Expand All @@ -477,3 +477,18 @@ export default {
preferenceSchema,
],
}

export const hydroNames = {
page: 'docs.v0.PageEvent',
exit: 'docs.v0.ExitEvent',
link: 'docs.v0.LinkEvent',
search: 'docs.v0.SearchEvent',
searchResult: 'docs.v0.SearchResultEvent',
navigate: 'docs.v0.NavigateEvent',
survey: 'docs.v0.SurveyEvent',
experiment: 'docs.v0.ExperimentEvent',
redirect: 'docs.v0.RedirectEvent',
clipboard: 'docs.v0.ClipboardEvent',
print: 'docs.v0.PrintEvent',
preference: 'docs.v0.PreferenceEvent',
}
6 changes: 3 additions & 3 deletions middleware/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import express from 'express'
import { omit } from 'lodash-es'
import Ajv from 'ajv'
import addFormats from 'ajv-formats'
import schema from '../lib/schema-event.js'
import { eventSchema, hydroNames } from '../lib/schema-event.js'

const OMIT_FIELDS = ['type']

Expand All @@ -15,14 +15,14 @@ router.post('/', async function postEvents(req, res, next) {
const isDev = process.env.NODE_ENV === 'development'
const fields = omit(req.body, '_csrf')

if (!ajv.validate(schema, fields)) {
if (!ajv.validate(eventSchema, fields)) {
return res.status(400).json(isDev ? ajv.errorsText() : {})
}

if (req.hydro.maySend()) {
// intentionally don't await this async request
// so that the http response afterwards is sent immediately
req.hydro.publish(req.hydro.schemas[fields.type], omit(fields, OMIT_FIELDS)).catch((e) => {
req.hydro.publish(hydroNames[fields.type], omit(fields, OMIT_FIELDS)).catch((e) => {
if (isDev) console.error(e)
})
}
Expand Down
21 changes: 15 additions & 6 deletions middleware/learning-track.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,22 @@ export default async function learningTrack(req, res, next) {

const currentLearningTrack = { trackName, trackProduct }
const guidePath = getPathWithoutLanguage(getPathWithoutVersion(req.pagePath))
let guideIndex = track.guides.findIndex((path) => path === guidePath)

// The raw track.guides will return all guide paths, need to use getLinkData
// so we only get guides available in the current version
const trackGuides = await getLinkData(track.guides, req.context)

const trackGuidePaths = trackGuides.map((guide) => {
return getPathWithoutLanguage(getPathWithoutVersion(guide.href))
})

let guideIndex = trackGuidePaths.findIndex((path) => path === guidePath)

// The learning track path may use Liquid version conditionals, handle the
// case where the requested path is a learning track path but won't match
// because of a Liquid conditional.
if (guideIndex < 0) {
guideIndex = await indexOfLearningTrackGuide(track.guides, guidePath, req.context)
guideIndex = await indexOfLearningTrackGuide(trackGuidePaths, guidePath, req.context)
}

// Also check if the learning track path is now a redirect to the requested
Expand All @@ -48,14 +57,14 @@ export default async function learningTrack(req, res, next) {
for (const redirect of req.context.page.redirect_from) {
if (guideIndex >= 0) break

guideIndex = await indexOfLearningTrackGuide(track.guides, redirect, req.context)
guideIndex = await indexOfLearningTrackGuide(trackGuidePaths, redirect, req.context)
}
}

if (guideIndex < 0) return noTrack()

if (guideIndex > 0) {
const prevGuidePath = track.guides[guideIndex - 1]
const prevGuidePath = trackGuidePaths[guideIndex - 1]
const result = await getLinkData(prevGuidePath, req.context, { title: true, intro: false })
if (!result) return noTrack()

Expand All @@ -64,8 +73,8 @@ export default async function learningTrack(req, res, next) {
currentLearningTrack.prevGuide = { href, title }
}

if (guideIndex < track.guides.length - 1) {
const nextGuidePath = track.guides[guideIndex + 1]
if (guideIndex < trackGuidePaths.length - 1) {
const nextGuidePath = trackGuidePaths[guideIndex + 1]
const result = await getLinkData(nextGuidePath, req.context, { title: true, intro: false })
if (!result) return noTrack()

Expand Down
24 changes: 0 additions & 24 deletions tests/unit/hydro.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,6 @@ describe('hydro', () => {
})
})

describe('#publishMany', () => {
it('publishes multiple events to Hydro', async () => {
await hydro.publishMany([
{ schema: 'event-name', value: { pizza: true } },
{ schema: 'other-name', value: { salad: false } },
])

expect(params).toEqual({
events: [
{
schema: 'event-name',
value: JSON.stringify({ pizza: true }),
cluster: 'potomac',
},
{
schema: 'other-name',
value: JSON.stringify({ salad: false }),
cluster: 'potomac',
},
],
})
})
})

describe('#generatePayloadHmac', () => {
it('returns a SHA256 HMAC string', () => {
const body = JSON.stringify({ pizza: true })
Expand Down