Skip to content

Commit

Permalink
Merge branch 'main' into repo-sync
Browse files Browse the repository at this point in the history
  • Loading branch information
Octomerger authored Nov 16, 2021
2 parents 58dcbeb + ed53e2d commit ddae8d1
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 121 deletions.
40 changes: 35 additions & 5 deletions content/billing/index.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,46 @@
---
title: Billing and payments for GitHub
title: Billing and payments on GitHub
shortTitle: Billing and payments
intro: '{% ifversion fpt %}{% data variables.product.product_name %} offers free and paid products for every account. You can upgrade, downgrade, and view pending changes to your account''s subscription at any time.{% elsif ghec or ghes or ghae %}{% data variables.product.company_short %} bills for your enterprise members'' {% ifversion ghec or ghae %}usage of {% data variables.product.product_name %}{% elsif ghes %} licence seats for {% data variables.product.product_name %}{% ifversion ghes > 3.0 %} and any additional services that you purchase{% endif %}{% endif %}.{% endif %}'
intro: '{% ifversion fpt %}{% data variables.product.product_name %} offers free and paid products for every account. You can upgrade or downgrade your account''s subscription and manage your billing settings at any time.{% elsif ghec or ghes or ghae %}{% data variables.product.company_short %} bills for your enterprise members'' {% ifversion ghec or ghae %}usage of {% data variables.product.product_name %}{% elsif ghes %} licence seats for {% data variables.product.product_name %}{% ifversion ghes > 3.0 %} and any additional services that you purchase{% endif %}{% endif %}. {% endif %}{% ifversion ghec %} You can view your subscription and manage your billing settings at any time. {% endif %}{% ifversion fpt or ghec %} You can also view usage and manage spending limits for {% data variables.product.product_name %} features such as {% data variables.product.prodname_actions %}, {% data variables.product.prodname_registry %}, and {% data variables.product.prodname_codespaces %}.{% endif %}'
redirect_from:
- /github/setting-up-and-managing-billing-and-payments-on-github
- /categories/setting-up-and-managing-billing-and-payments-on-github
introLinks:
overview: '{% ifversion fpt or ghec %}/billing/managing-your-github-billing-settings/about-billing-on-github{% elsif ghes%}/billing/managing-billing-for-your-github-account/about-billing-for-your-enterprise{% endif %}'
featuredLinks:
guides:
- '{% ifversion fpt or ghec %}/billing/managing-your-github-billing-settings/adding-or-editing-a-payment-method{% endif %}'
- '{% ifversion fpt %}/billing/managing-billing-for-your-github-account/upgrading-your-github-subscription{% endif %}'
- '{% ifversion ghec %}/billing/managing-billing-for-your-github-account/about-billing-for-your-enterprise{% endif %}'
- '{% ifversion fpt or ghec %}/billing/managing-your-github-billing-settings/setting-your-billing-email{% endif %}'
- '{% ifversion fpt or ghec %}/billing/managing-billing-for-your-github-account/about-per-user-pricing{% endif %}'
- '{% ifversion ghes %}/billing/managing-billing-for-your-github-account/viewing-the-subscription-and-usage-for-your-enterprise-account{% endif %}'
- '{% ifversion ghes %}/billing/managing-your-license-for-github-enterprise/about-licenses-for-github-enterprise{% endif %}'
- '{% ifversion ghes %}/billing/managing-your-license-for-github-enterprise/viewing-license-usage-for-github-enterprise{% endif %}'
- '{% ifversion ghae %}/billing/managing-billing-for-your-github-account/about-billing-for-your-enterprise{% endif %}'
popular:
- '{% ifversion ghec %}/billing/managing-billing-for-your-github-account/viewing-the-subscription-and-usage-for-your-enterprise-account{% endif %}'
- '{% ifversion fpt or ghec %}/billing/managing-billing-for-your-github-account/downgrading-your-github-subscription{% endif %}'
- '{% ifversion fpt or ghec %}/billing/managing-billing-for-github-actions/about-billing-for-github-actions{% endif %}'
- '{% ifversion fpt or ghec %}/billing/managing-billing-for-github-codespaces/about-billing-for-codespaces{% endif %}'
- '{% ifversion ghes %}/billing/managing-billing-for-github-advanced-security/about-billing-for-github-advanced-security{% endif %}'
- '{% ifversion ghes %}/billing/managing-billing-for-github-advanced-security/viewing-your-github-advanced-security-usage{% endif %}'
- '{% ifversion ghes %}/billing/managing-your-license-for-github-enterprise/uploading-a-new-license-to-github-enterprise-server{% endif %}'
- '{% ifversion ghae %}/billing/managing-billing-for-your-github-account/about-billing-for-your-enterprise{% endif %}'
guideCards:
- /billing/managing-your-github-billing-settings/removing-a-payment-method
- /billing/managing-billing-for-your-github-account/how-does-upgrading-or-downgrading-affect-the-billing-process
- /billing/managing-billing-for-git-large-file-storage/upgrading-git-large-file-storage
- '{% ifversion ghes %}/billing/managing-your-license-for-github-enterprise/downloading-your-license-for-github-enterprise{% endif %}'
- '{% ifversion ghes %}/billing/managing-your-license-for-github-enterprise/syncing-license-usage-between-github-enterprise-server-and-github-enterprise-cloud{% endif %}'
layout: product-landing
versions:
fpt: '*'
ghec: '*'
ghes: '*'
ghae: '*'
ghec: '*'
topics:
- Billing
children:
- /managing-your-github-billing-settings
- /managing-billing-for-your-github-account
Expand All @@ -23,5 +54,4 @@ children:
- /managing-billing-for-github-marketplace-apps
- /managing-billing-for-git-large-file-storage
- /setting-up-paid-organizations-for-procurement-companies
---

---
132 changes: 27 additions & 105 deletions lib/failbot.js
Original file line number Diff line number Diff line change
@@ -1,112 +1,34 @@
import fetch from 'node-fetch'
import got from 'got'
import { Failbot, HTTPBackend, LogBackend } from '@github/failbot'

export default class FailBot {
constructor({ app, haystackURL, headers }) {
this.app = app
this.headers = headers
const HAYSTACK_APP = 'docs'

// Since we're using `node-fetch` we can't rely on it deconstructing the
// basic authentication credentials from the URL (e.g.
// https://user:pass@failbotdomain/path) because `node-fetch` will always
// strip it. See https://github.com/node-fetch/node-fetch/issues/1330
// and it's not a bug.
// The correct thing is to extract it manually and add an `Authorization`
// header based on it from the URL.
const url = new URL(haystackURL)
export function report(error, metadata) {
// If there's no HAYSTACK_URL set, bail early
if (!process.env.HAYSTACK_URL) return

// remove the basic auth portion of the url since it throws an error in node-fetch
this.haystackURL = `${url.origin}${url.pathname}`

const { username, password } = url
if (username || password) {
this.headers.Authorization = `Basic ${Buffer.from(`${username}:${password}`).toString(
'base64'
)}`
} else {
console.warn(`The haystack URL does not contain authentication credentials`)
}
}

/**
* Report an error to Sentry
* @param {Error} error
* @param {any} metadata
* @param {any} [headers]
*/
static async report(error, metadata, headers = {}) {
// If there's no HAYSTACK_URL set, bail early
if (!process.env.HAYSTACK_URL) return

const failbot = new FailBot({
app: 'docs',
const backends = [
new HTTPBackend({
haystackURL: process.env.HAYSTACK_URL,
headers,
})

return failbot.sendException(error, metadata)
}

/**
* Create a rollup of this error by generating a base64 representation
* @param {Error} error
*/
createRollup(error) {
const stackLine = error.stack && error.stack.split('\n')[1]
const str = `${error.name}:${stackLine}`.replace(/=/g, '')
return Buffer.from(str).toString('base64')
}

/**
* Format the error to a plain JSON object with additional data
* @param {Error} error
* @param {any} metadata
*/
formatJSON(error, metadata) {
return Object.assign({}, metadata, {
/* eslint-disable camelcase */
created_at: new Date().toISOString(),
rollup: this.createRollup(error),
class: error.name,
message: error.message,
backtrace: error.stack || '',
js_environment: `Node.js ${process.version}`,
/* eslint-enable camelcase */
})
}

/**
* Populate default context from settings. Since settings commonly comes from
* ENV, this allows setting defaults for the context via the environment.
*/
getFailbotContext() {
const failbotKeys = {}

for (const key in process.env) {
if (key.startsWith('FAILBOT_CONTEXT_')) {
const formattedKey = key.replace(/^FAILBOT_CONTEXT_/, '').toLowerCase()
failbotKeys[formattedKey] = process.env[key]
}
}

return failbotKeys
fetchFn: got,
}),
]
if (process.env.NODE_ENV !== 'test') {
backends.push(new LogBackend({ log: console.log.bind(console) }))
}
const failbot = new Failbot({
app: HAYSTACK_APP,
backends: backends,
})
return failbot.report(error, metadata)
}

/**
* Send the error to Sentry
* @param {Error} error
* @param {any} metadata
*/
async sendException(error, metadata = {}) {
const data = Object.assign({ app: this.app }, this.getFailbotContext(), metadata)
const body = this.formatJSON(error, Object.assign({ app: this.app }, data))

return fetch(this.haystackURL, {
method: 'POST',
body: JSON.stringify(body),
headers: {
...this.headers,
'Content-Type': 'application/json',
},
})
}
// Kept for legacy so you can continue to do:
//
// import FailBot from './lib/failbot.js'
// ...
// FailBot.report(myError)
//
export default {
report,
}
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
],
"dependencies": {
"@alex_neo/jest-expect-message": "^1.0.5",
"@github/failbot": "0.7.0",
"@hapi/accept": "^5.0.2",
"@primer/components": "^31.1.0",
"@primer/css": "^18.2.0",
Expand Down
32 changes: 21 additions & 11 deletions tests/unit/failbot.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@ import FailBot from '../../lib/failbot.js'
import nock from 'nock'

describe('FailBot', () => {
const requestBodiesSent = []

beforeEach(() => {
nock('https://haystack.com')
nock('https://haystack.example.com')
.post('/')
.reply(200, (uri, requestBody) => {
requestBodiesSent.push(requestBody)
return requestBody
})
})

afterEach(() => {
delete process.env.HAYSTACK_URL
// Reset the array to an empty one between tests
// so it doesn't intefere across tests.
requestBodiesSent.length = 0
})

describe('.report', () => {
Expand All @@ -21,19 +27,23 @@ describe('FailBot', () => {
})

it('sends the expected report', async () => {
process.env.HAYSTACK_URL = 'https://haystack.com'
process.env.HAYSTACK_URL = 'https://haystack.example.com'
const err = new Error('Kaboom')
const result = await FailBot.report(err)

// Check that we made a request
expect(result.status).toBe(200)
const backendPromises = FailBot.report(err, { foo: 'bar' })
// Note! You don't need to await the promises it returns to be
// able to use `FailBot.report()`. It will send.
// But here in the context of jest, we need to await *now*
// so we can assert that it did make the relevant post requests.
// Once we've done this, we can immediate check what it did.
await Promise.all(await backendPromises)

// Verify the basic fetch params
expect(result.headers.get('content-type')).toBe('application/json')
// It's not interesting or relevant what the `.report()` static
// method returns. All that matters is that it did a POST
// request.
expect(requestBodiesSent.length).toBe(1)

// Check that we send the expected body
const body = await result.json()
expect(body).toMatchObject({
// Verify what was sent in that POST request.
expect(requestBodiesSent[0]).toMatchObject({
app: 'docs',
backtrace: expect.stringContaining('Error: Kaboom'),
class: 'Error',
Expand Down

0 comments on commit ddae8d1

Please sign in to comment.