diff --git a/.github/allowed-actions.js b/.github/allowed-actions.js
index 6450852204..9d2d7400e2 100644
--- a/.github/allowed-actions.js
+++ b/.github/allowed-actions.js
@@ -30,7 +30,7 @@ module.exports = [
'rachmari/labeler@832d42ec5523f3c6d46e8168de71cd54363e3e2e',
'repo-sync/github-sync@3832fe8e2be32372e1b3970bbae8e7079edeec88',
'repo-sync/pull-request@33777245b1aace1a58c87a29c90321aa7a74bd7d',
- 'rtCamp/action-slack-notify@e17352feaf9aee300bf0ebc1dfbf467d80438815',
+ 'someimportantcompany/github-actions-slack-message@0b470c14b39da4260ed9e3f9a4f1298a74ccdefd',
'tjenkinson/gh-action-auto-merge-dependency-updates@cee2ac0',
'EndBug/add-and-commit@9358097a71ad9fb9e2f9624c6098c89193d83575'
]
diff --git a/.github/workflows/js-lint.yml b/.github/workflows/js-lint.yml
index 3da0e854a2..4c138c5955 100644
--- a/.github/workflows/js-lint.yml
+++ b/.github/workflows/js-lint.yml
@@ -10,23 +10,8 @@ on:
- translations
jobs:
- see_if_should_skip:
- runs-on: ubuntu-latest
-
- outputs:
- should_skip: ${{ steps.skip_check.outputs.should_skip }}
- steps:
- - id: skip_check
- uses: fkirc/skip-duplicate-actions@36feb0d8d062137530c2e00bd278d138fe191289
- with:
- cancel_others: 'false'
- github_token: ${{ github.token }}
- paths: '["**/*.js", "package*.json", ".github/workflows/js-lint.yml", ".eslint*"]'
-
lint:
runs-on: ubuntu-latest
- needs: see_if_should_skip
- if: ${{ needs.see_if_should_skip.outputs.should_skip != 'true' }}
steps:
- name: Check out repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
diff --git a/.github/workflows/repo-freeze-reminders.yml b/.github/workflows/repo-freeze-reminders.yml
index e6f7eb479e..f3b166cd87 100644
--- a/.github/workflows/repo-freeze-reminders.yml
+++ b/.github/workflows/repo-freeze-reminders.yml
@@ -14,11 +14,10 @@ jobs:
if: github.repository == 'github/docs-internal'
steps:
- name: Send Slack notification if repo is frozen
+ uses: someimportantcompany/github-actions-slack-message@0b470c14b39da4260ed9e3f9a4f1298a74ccdefd
if: ${{ env.FREEZE == 'true' }}
- uses: rtCamp/action-slack-notify@e17352feaf9aee300bf0ebc1dfbf467d80438815
- env:
- SLACK_WEBHOOK: ${{ secrets.DOCS_ALERTS_SLACK_WEBHOOK }}
- SLACK_USERNAME: docs-repo-sync
- SLACK_ICON_EMOJI: ':freezing_face:'
- SLACK_COLOR: '#51A0D5' # Carolina Blue
- SLACK_MESSAGE: All repo-sync runs will fail for ${{ github.repository }} because the repo is currently frozen!
+ with:
+ channel: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
+ bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
+ color: info
+ text: All repo-sync runs will fail for ${{ github.repository }} because the repo is currently frozen!
diff --git a/.github/workflows/repo-sync-stalls.yml b/.github/workflows/repo-sync-stalls.yml
new file mode 100644
index 0000000000..fb605313c3
--- /dev/null
+++ b/.github/workflows/repo-sync-stalls.yml
@@ -0,0 +1,54 @@
+name: Repo Sync Stalls
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '*/30 * * * *'
+jobs:
+ check-freezer:
+ name: Check for deployment freezes
+ runs-on: ubuntu-latest
+ steps:
+ - name: Exit if repo is frozen
+ if: ${{ env.FREEZE == 'true' }}
+ run: |
+ echo 'The repo is currently frozen! Exiting this workflow.'
+ exit 1 # prevents further steps from running
+ repo-sync-stalls:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check if repo sync is stalled
+ uses: actions/github-script@626af12fe9a53dc2972b48385e7fe7dec79145c9
+ with:
+ github-token: ${{ secrets.DOCUBOT_FR_PROJECT_BOARD_WORKFLOWS_REPO_ORG_READ_SCOPES }}
+ script: |
+ let pulls;
+ const owner = context.repo.owner
+ const repo = context.repo.repo
+ try {
+ pulls = await github.pulls.list({
+ owner: owner,
+ repo: repo,
+ head: `${owner}:repo-sync`,
+ state: 'open'
+ });
+ } catch(err) {
+ throw err
+ return
+ }
+
+ pulls.data.forEach(pr => {
+ const timeDelta = Date.now() - Date.parse(pr.created_at);
+ const minutesOpen = timeDelta / 1000 / 60;
+
+ if (minutesOpen > 30) {
+ core.setFailed('Repo sync appears to be stalled')
+ }
+ })
+ - name: Send Slack notification if workflow fails
+ uses: someimportantcompany/github-actions-slack-message@0b470c14b39da4260ed9e3f9a4f1298a74ccdefd
+ if: failure()
+ with:
+ channel: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
+ bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
+ color: failure
+ text: Repo sync appears to be stalled for ${{github.repository}}. See https://github.com/${{github.repository}}/pulls?q=is%3Apr+is%3Aopen+repo+sync
diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml
index b33242e90b..298eab5668 100644
--- a/.github/workflows/repo-sync.yml
+++ b/.github/workflows/repo-sync.yml
@@ -7,6 +7,7 @@
name: Repo Sync
on:
+ workflow_dispatch:
schedule:
- cron: '*/15 * * * *' # every 15 minutes
@@ -70,11 +71,10 @@ jobs:
number: ${{ steps.find-pull-request.outputs.number }}
- name: Send Slack notification if workflow fails
- uses: rtCamp/action-slack-notify@e17352feaf9aee300bf0ebc1dfbf467d80438815
- if: ${{ failure() }}
- env:
- SLACK_WEBHOOK: ${{ secrets.DOCS_ALERTS_SLACK_WEBHOOK }}
- SLACK_USERNAME: docs-repo-sync
- SLACK_ICON_EMOJI: ':ohno:'
- SLACK_COLOR: '#B90E0A' # Crimson
- SLACK_MESSAGE: The last repo-sync run for ${{github.repository}} failed. See https://github.com/${{github.repository}}/actions?query=workflow%3A%22Repo+Sync%22
+ uses: someimportantcompany/github-actions-slack-message@0b470c14b39da4260ed9e3f9a4f1298a74ccdefd
+ if: failure()
+ with:
+ channel: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
+ bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
+ color: failure
+ text: The last repo-sync run for ${{github.repository}} failed. See https://github.com/${{github.repository}}/actions?query=workflow%3A%22Repo+Sync%22
diff --git a/.github/workflows/sync-algolia-search-indices.yml b/.github/workflows/sync-algolia-search-indices.yml
index a7f9ac3b49..512b3b0e0c 100644
--- a/.github/workflows/sync-algolia-search-indices.yml
+++ b/.github/workflows/sync-algolia-search-indices.yml
@@ -33,8 +33,10 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm run sync-search
- name: Send slack notification if workflow run fails
- uses: rtCamp/action-slack-notify@e17352feaf9aee300bf0ebc1dfbf467d80438815
+ uses: someimportantcompany/github-actions-slack-message@0b470c14b39da4260ed9e3f9a4f1298a74ccdefd
if: failure()
- env:
- SLACK_WEBHOOK: ${{ secrets.DOCS_ALERTS_SLACK_WEBHOOK }}
- SLACK_MESSAGE: The last Algolia workflow run for ${{github.repository}} failed. Search actions for `workflow:Algolia`
+ with:
+ channel: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
+ bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
+ color: failure
+ text: The last Algolia workflow run for ${{github.repository}} failed. Search actions for `workflow:Algolia`
diff --git a/.github/workflows/yml-lint.yml b/.github/workflows/yml-lint.yml
index 69d665ecde..f8248961e0 100644
--- a/.github/workflows/yml-lint.yml
+++ b/.github/workflows/yml-lint.yml
@@ -10,23 +10,8 @@ on:
- translations
jobs:
- see_if_should_skip:
- runs-on: ubuntu-latest
-
- outputs:
- should_skip: ${{ steps.skip_check.outputs.should_skip }}
- steps:
- - id: skip_check
- uses: fkirc/skip-duplicate-actions@36feb0d8d062137530c2e00bd278d138fe191289
- with:
- cancel_others: 'false'
- github_token: ${{ github.token }}
- paths: '["**/*.yml", "**/*.yaml", "package*.json", ".github/workflows/yml-lint.yml"]'
-
lint:
runs-on: ubuntu-latest
- needs: see_if_should_skip
- if: ${{ needs.see_if_should_skip.outputs.should_skip != 'true' }}
steps:
- name: Check out repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
diff --git a/assets/images/help/classroom/assignment-group-hero.png b/assets/images/help/classroom/assignment-group-hero.png
new file mode 100644
index 0000000000..22eb83fde9
Binary files /dev/null and b/assets/images/help/classroom/assignment-group-hero.png differ
diff --git a/assets/images/help/classroom/assignment-ide-go-grant-access-button.png b/assets/images/help/classroom/assignment-ide-go-grant-access-button.png
new file mode 100644
index 0000000000..14aa7906ec
Binary files /dev/null and b/assets/images/help/classroom/assignment-ide-go-grant-access-button.png differ
diff --git a/assets/images/help/classroom/assignment-individual-hero.png b/assets/images/help/classroom/assignment-individual-hero.png
new file mode 100644
index 0000000000..7f49a1e22d
Binary files /dev/null and b/assets/images/help/classroom/assignment-individual-hero.png differ
diff --git a/assets/images/help/classroom/assignment-repository-ide-button-in-readme.png b/assets/images/help/classroom/assignment-repository-ide-button-in-readme.png
new file mode 100644
index 0000000000..84ecbffb72
Binary files /dev/null and b/assets/images/help/classroom/assignment-repository-ide-button-in-readme.png differ
diff --git a/assets/images/help/classroom/assignments-assign-deadline.png b/assets/images/help/classroom/assignments-assign-deadline.png
new file mode 100644
index 0000000000..b31c5c39b1
Binary files /dev/null and b/assets/images/help/classroom/assignments-assign-deadline.png differ
diff --git a/assets/images/help/classroom/assignments-assignment-title.png b/assets/images/help/classroom/assignments-assignment-title.png
new file mode 100644
index 0000000000..c842ea539b
Binary files /dev/null and b/assets/images/help/classroom/assignments-assignment-title.png differ
diff --git a/assets/images/help/classroom/assignments-autograding-click-pencil-or-trash.png b/assets/images/help/classroom/assignments-autograding-click-pencil-or-trash.png
new file mode 100644
index 0000000000..6d07e254a9
Binary files /dev/null and b/assets/images/help/classroom/assignments-autograding-click-pencil-or-trash.png differ
diff --git a/assets/images/help/classroom/assignments-choose-repository-visibility.png b/assets/images/help/classroom/assignments-choose-repository-visibility.png
new file mode 100644
index 0000000000..4bc310043a
Binary files /dev/null and b/assets/images/help/classroom/assignments-choose-repository-visibility.png differ
diff --git a/assets/images/help/classroom/assignments-click-continue-button.png b/assets/images/help/classroom/assignments-click-continue-button.png
new file mode 100644
index 0000000000..78325959af
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-continue-button.png differ
diff --git a/assets/images/help/classroom/assignments-click-create-assignment-button.png b/assets/images/help/classroom/assignments-click-create-assignment-button.png
new file mode 100644
index 0000000000..44f1a1e517
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-create-assignment-button.png differ
diff --git a/assets/images/help/classroom/assignments-click-grading-and-feedback.png b/assets/images/help/classroom/assignments-click-grading-and-feedback.png
new file mode 100644
index 0000000000..fddaa731d1
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-grading-and-feedback.png differ
diff --git a/assets/images/help/classroom/assignments-click-new-assignment-button.png b/assets/images/help/classroom/assignments-click-new-assignment-button.png
new file mode 100644
index 0000000000..278afe63fa
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-new-assignment-button.png differ
diff --git a/assets/images/help/classroom/assignments-click-online-ide.png b/assets/images/help/classroom/assignments-click-online-ide.png
new file mode 100644
index 0000000000..e07bc69905
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-online-ide.png differ
diff --git a/assets/images/help/classroom/assignments-click-pencil.png b/assets/images/help/classroom/assignments-click-pencil.png
new file mode 100644
index 0000000000..81e027c7f2
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-pencil.png differ
diff --git a/assets/images/help/classroom/assignments-click-review-button.png b/assets/images/help/classroom/assignments-click-review-button.png
new file mode 100644
index 0000000000..1f04f152a8
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-review-button.png differ
diff --git a/assets/images/help/classroom/assignments-click-save-test-case-button.png b/assets/images/help/classroom/assignments-click-save-test-case-button.png
new file mode 100644
index 0000000000..6336d308ad
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-save-test-case-button.png differ
diff --git a/assets/images/help/classroom/assignments-click-template-repository-in-list.png b/assets/images/help/classroom/assignments-click-template-repository-in-list.png
new file mode 100644
index 0000000000..0be6b1153c
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-template-repository-in-list.png differ
diff --git a/assets/images/help/classroom/assignments-click-update-assignment.png b/assets/images/help/classroom/assignments-click-update-assignment.png
new file mode 100644
index 0000000000..9423ea66ef
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-update-assignment.png differ
diff --git a/assets/images/help/classroom/assignments-click-view-ide.png b/assets/images/help/classroom/assignments-click-view-ide.png
new file mode 100644
index 0000000000..496ebe4cc3
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-view-ide.png differ
diff --git a/assets/images/help/classroom/assignments-click-view-test.png b/assets/images/help/classroom/assignments-click-view-test.png
new file mode 100644
index 0000000000..e7fbb4931a
Binary files /dev/null and b/assets/images/help/classroom/assignments-click-view-test.png differ
diff --git a/assets/images/help/classroom/assignments-define-teams.png b/assets/images/help/classroom/assignments-define-teams.png
new file mode 100644
index 0000000000..d72077eb55
Binary files /dev/null and b/assets/images/help/classroom/assignments-define-teams.png differ
diff --git a/assets/images/help/classroom/assignments-enable-feedback-pull-requests.png b/assets/images/help/classroom/assignments-enable-feedback-pull-requests.png
new file mode 100644
index 0000000000..7597b5fe91
Binary files /dev/null and b/assets/images/help/classroom/assignments-enable-feedback-pull-requests.png differ
diff --git a/assets/images/help/classroom/assignments-type-protected-file-paths.png b/assets/images/help/classroom/assignments-type-protected-file-paths.png
new file mode 100644
index 0000000000..b52d8bcd18
Binary files /dev/null and b/assets/images/help/classroom/assignments-type-protected-file-paths.png differ
diff --git a/assets/images/help/classroom/autograding-actions-logs.png b/assets/images/help/classroom/autograding-actions-logs.png
new file mode 100644
index 0000000000..7c0ee70fda
Binary files /dev/null and b/assets/images/help/classroom/autograding-actions-logs.png differ
diff --git a/assets/images/help/classroom/autograding-actions-tab.png b/assets/images/help/classroom/autograding-actions-tab.png
new file mode 100644
index 0000000000..68c6606fd5
Binary files /dev/null and b/assets/images/help/classroom/autograding-actions-tab.png differ
diff --git a/assets/images/help/classroom/autograding-click-grading-method.png b/assets/images/help/classroom/autograding-click-grading-method.png
new file mode 100644
index 0000000000..ca8e19417e
Binary files /dev/null and b/assets/images/help/classroom/autograding-click-grading-method.png differ
diff --git a/assets/images/help/classroom/autograding-click-pencil.png b/assets/images/help/classroom/autograding-click-pencil.png
new file mode 100644
index 0000000000..0b2e38ee3b
Binary files /dev/null and b/assets/images/help/classroom/autograding-click-pencil.png differ
diff --git a/assets/images/help/classroom/autograding-click-trash.png b/assets/images/help/classroom/autograding-click-trash.png
new file mode 100644
index 0000000000..687a2d98f9
Binary files /dev/null and b/assets/images/help/classroom/autograding-click-trash.png differ
diff --git a/assets/images/help/classroom/autograding-hero.png b/assets/images/help/classroom/autograding-hero.png
new file mode 100644
index 0000000000..a831cdadea
Binary files /dev/null and b/assets/images/help/classroom/autograding-hero.png differ
diff --git a/assets/images/help/classroom/classroom-add-students-to-your-roster.png b/assets/images/help/classroom/classroom-add-students-to-your-roster.png
new file mode 100644
index 0000000000..c44e8d6461
Binary files /dev/null and b/assets/images/help/classroom/classroom-add-students-to-your-roster.png differ
diff --git a/assets/images/help/classroom/classroom-copy-credentials.png b/assets/images/help/classroom/classroom-copy-credentials.png
new file mode 100644
index 0000000000..7afe923e46
Binary files /dev/null and b/assets/images/help/classroom/classroom-copy-credentials.png differ
diff --git a/assets/images/help/classroom/classroom-hero.png b/assets/images/help/classroom/classroom-hero.png
new file mode 100644
index 0000000000..52c66bed2c
Binary files /dev/null and b/assets/images/help/classroom/classroom-hero.png differ
diff --git a/assets/images/help/classroom/classroom-settings-click-connection-settings.png b/assets/images/help/classroom/classroom-settings-click-connection-settings.png
new file mode 100644
index 0000000000..0a48461fd8
Binary files /dev/null and b/assets/images/help/classroom/classroom-settings-click-connection-settings.png differ
diff --git a/assets/images/help/classroom/classroom-settings-click-disconnect-from-your-lms-button.png b/assets/images/help/classroom/classroom-settings-click-disconnect-from-your-lms-button.png
new file mode 100644
index 0000000000..45f20305f0
Binary files /dev/null and b/assets/images/help/classroom/classroom-settings-click-disconnect-from-your-lms-button.png differ
diff --git a/assets/images/help/classroom/classroom-settings-click-lms.png b/assets/images/help/classroom/classroom-settings-click-lms.png
new file mode 100644
index 0000000000..f2548ad331
Binary files /dev/null and b/assets/images/help/classroom/classroom-settings-click-lms.png differ
diff --git a/assets/images/help/classroom/click-assignment-in-list.png b/assets/images/help/classroom/click-assignment-in-list.png
new file mode 100644
index 0000000000..6dd4f09bf7
Binary files /dev/null and b/assets/images/help/classroom/click-assignment-in-list.png differ
diff --git a/assets/images/help/classroom/click-classroom-in-list.png b/assets/images/help/classroom/click-classroom-in-list.png
new file mode 100644
index 0000000000..5f4f28de54
Binary files /dev/null and b/assets/images/help/classroom/click-classroom-in-list.png differ
diff --git a/assets/images/help/classroom/click-create-classroom-button.png b/assets/images/help/classroom/click-create-classroom-button.png
new file mode 100644
index 0000000000..7ca72c57e9
Binary files /dev/null and b/assets/images/help/classroom/click-create-classroom-button.png differ
diff --git a/assets/images/help/classroom/click-create-roster-button.png b/assets/images/help/classroom/click-create-roster-button.png
new file mode 100644
index 0000000000..066af68fe1
Binary files /dev/null and b/assets/images/help/classroom/click-create-roster-button.png differ
diff --git a/assets/images/help/classroom/click-delete-classroom-button.png b/assets/images/help/classroom/click-delete-classroom-button.png
new file mode 100644
index 0000000000..87fda14697
Binary files /dev/null and b/assets/images/help/classroom/click-delete-classroom-button.png differ
diff --git a/assets/images/help/classroom/click-import-from-a-learning-management-system-button.png b/assets/images/help/classroom/click-import-from-a-learning-management-system-button.png
new file mode 100644
index 0000000000..62c6cde46a
Binary files /dev/null and b/assets/images/help/classroom/click-import-from-a-learning-management-system-button.png differ
diff --git a/assets/images/help/classroom/click-new-classroom-button.png b/assets/images/help/classroom/click-new-classroom-button.png
new file mode 100644
index 0000000000..a2cdf4cd41
Binary files /dev/null and b/assets/images/help/classroom/click-new-classroom-button.png differ
diff --git a/assets/images/help/classroom/click-organization.png b/assets/images/help/classroom/click-organization.png
new file mode 100644
index 0000000000..8bc5c40e15
Binary files /dev/null and b/assets/images/help/classroom/click-organization.png differ
diff --git a/assets/images/help/classroom/click-settings.png b/assets/images/help/classroom/click-settings.png
new file mode 100644
index 0000000000..e074c6c1eb
Binary files /dev/null and b/assets/images/help/classroom/click-settings.png differ
diff --git a/assets/images/help/classroom/click-students.png b/assets/images/help/classroom/click-students.png
new file mode 100644
index 0000000000..48ef5de6da
Binary files /dev/null and b/assets/images/help/classroom/click-students.png differ
diff --git a/assets/images/help/classroom/click-update-students-button.png b/assets/images/help/classroom/click-update-students-button.png
new file mode 100644
index 0000000000..116e06a99c
Binary files /dev/null and b/assets/images/help/classroom/click-update-students-button.png differ
diff --git a/assets/images/help/classroom/delete-classroom-click-delete-classroom-button.png b/assets/images/help/classroom/delete-classroom-click-delete-classroom-button.png
new file mode 100644
index 0000000000..9a541ae2a9
Binary files /dev/null and b/assets/images/help/classroom/delete-classroom-click-delete-classroom-button.png differ
diff --git a/assets/images/help/classroom/delete-classroom-modal-with-warning.png b/assets/images/help/classroom/delete-classroom-modal-with-warning.png
new file mode 100644
index 0000000000..fb7047c040
Binary files /dev/null and b/assets/images/help/classroom/delete-classroom-modal-with-warning.png differ
diff --git a/assets/images/help/classroom/ide-makecode-arcade-version-control-button.png b/assets/images/help/classroom/ide-makecode-arcade-version-control-button.png
new file mode 100644
index 0000000000..ab17492f6a
Binary files /dev/null and b/assets/images/help/classroom/ide-makecode-arcade-version-control-button.png differ
diff --git a/assets/images/help/classroom/ide-replit-version-control-button.png b/assets/images/help/classroom/ide-replit-version-control-button.png
new file mode 100644
index 0000000000..2ce9536f96
Binary files /dev/null and b/assets/images/help/classroom/ide-replit-version-control-button.png differ
diff --git a/assets/images/help/classroom/lms-github-classroom-credentials.png b/assets/images/help/classroom/lms-github-classroom-credentials.png
new file mode 100644
index 0000000000..8d57a7b2fb
Binary files /dev/null and b/assets/images/help/classroom/lms-github-classroom-credentials.png differ
diff --git a/assets/images/help/classroom/probot-settings.gif b/assets/images/help/classroom/probot-settings.gif
new file mode 100644
index 0000000000..ee1a593361
Binary files /dev/null and b/assets/images/help/classroom/probot-settings.gif differ
diff --git a/assets/images/help/classroom/roster-hero.png b/assets/images/help/classroom/roster-hero.png
new file mode 100644
index 0000000000..8ce84cefd7
Binary files /dev/null and b/assets/images/help/classroom/roster-hero.png differ
diff --git a/assets/images/help/classroom/settings-click-rename-classroom-button.png b/assets/images/help/classroom/settings-click-rename-classroom-button.png
new file mode 100644
index 0000000000..f857bf63d3
Binary files /dev/null and b/assets/images/help/classroom/settings-click-rename-classroom-button.png differ
diff --git a/assets/images/help/classroom/settings-type-classroom-name.png b/assets/images/help/classroom/settings-type-classroom-name.png
new file mode 100644
index 0000000000..2c238cc516
Binary files /dev/null and b/assets/images/help/classroom/settings-type-classroom-name.png differ
diff --git a/assets/images/help/classroom/setup-click-authorize-github-classroom.png b/assets/images/help/classroom/setup-click-authorize-github-classroom.png
new file mode 100644
index 0000000000..08ec394c6e
Binary files /dev/null and b/assets/images/help/classroom/setup-click-authorize-github-classroom.png differ
diff --git a/assets/images/help/classroom/setup-click-authorize-github.png b/assets/images/help/classroom/setup-click-authorize-github.png
new file mode 100644
index 0000000000..3c1fcdd9d0
Binary files /dev/null and b/assets/images/help/classroom/setup-click-authorize-github.png differ
diff --git a/assets/images/help/classroom/setup-click-grant.png b/assets/images/help/classroom/setup-click-grant.png
new file mode 100644
index 0000000000..70942ecafd
Binary files /dev/null and b/assets/images/help/classroom/setup-click-grant.png differ
diff --git a/assets/images/help/classroom/students-click-delete-roster-button-in-modal.png b/assets/images/help/classroom/students-click-delete-roster-button-in-modal.png
new file mode 100644
index 0000000000..efadfac2a6
Binary files /dev/null and b/assets/images/help/classroom/students-click-delete-roster-button-in-modal.png differ
diff --git a/assets/images/help/classroom/students-click-delete-roster-button.png b/assets/images/help/classroom/students-click-delete-roster-button.png
new file mode 100644
index 0000000000..bb4fecc5d8
Binary files /dev/null and b/assets/images/help/classroom/students-click-delete-roster-button.png differ
diff --git a/assets/images/help/classroom/type-classroom-name.png b/assets/images/help/classroom/type-classroom-name.png
new file mode 100644
index 0000000000..3b407b2f40
Binary files /dev/null and b/assets/images/help/classroom/type-classroom-name.png differ
diff --git a/assets/images/help/classroom/type-or-upload-student-identifiers.png b/assets/images/help/classroom/type-or-upload-student-identifiers.png
new file mode 100644
index 0000000000..b1c006c5a1
Binary files /dev/null and b/assets/images/help/classroom/type-or-upload-student-identifiers.png differ
diff --git a/assets/images/help/classroom/use-drop-down-then-click-archive.png b/assets/images/help/classroom/use-drop-down-then-click-archive.png
new file mode 100644
index 0000000000..1fd2b0f0ef
Binary files /dev/null and b/assets/images/help/classroom/use-drop-down-then-click-archive.png differ
diff --git a/assets/images/help/classroom/use-drop-down-then-click-unarchive.png b/assets/images/help/classroom/use-drop-down-then-click-unarchive.png
new file mode 100644
index 0000000000..7e9ead3102
Binary files /dev/null and b/assets/images/help/classroom/use-drop-down-then-click-unarchive.png differ
diff --git a/assets/images/help/discussions/public-repo-settings.png b/assets/images/help/discussions/public-repo-settings.png
new file mode 100644
index 0000000000..c337a3a220
Binary files /dev/null and b/assets/images/help/discussions/public-repo-settings.png differ
diff --git a/assets/images/help/education/click-get-teacher-benefits.png b/assets/images/help/education/click-get-teacher-benefits.png
new file mode 100644
index 0000000000..65c68aafdc
Binary files /dev/null and b/assets/images/help/education/click-get-teacher-benefits.png differ
diff --git a/assets/images/help/images/overview-actions-result-navigate.png b/assets/images/help/images/overview-actions-result-navigate.png
new file mode 100644
index 0000000000..819f0e75e7
Binary files /dev/null and b/assets/images/help/images/overview-actions-result-navigate.png differ
diff --git a/assets/images/help/images/overview-actions-result-updated-2.png b/assets/images/help/images/overview-actions-result-updated-2.png
new file mode 100644
index 0000000000..7035583127
Binary files /dev/null and b/assets/images/help/images/overview-actions-result-updated-2.png differ
diff --git a/assets/images/help/images/workflow-graph-job.png b/assets/images/help/images/workflow-graph-job.png
new file mode 100644
index 0000000000..7aafcb5d89
Binary files /dev/null and b/assets/images/help/images/workflow-graph-job.png differ
diff --git a/assets/images/help/images/workflow-graph.png b/assets/images/help/images/workflow-graph.png
new file mode 100644
index 0000000000..ebd1b6a07c
Binary files /dev/null and b/assets/images/help/images/workflow-graph.png differ
diff --git a/assets/images/help/organizations/update-profile-button.png b/assets/images/help/organizations/update-profile-button.png
new file mode 100644
index 0000000000..bb701e7aed
Binary files /dev/null and b/assets/images/help/organizations/update-profile-button.png differ
diff --git a/assets/images/help/pull_requests/dependency-review-rich-diff.png b/assets/images/help/pull_requests/dependency-review-rich-diff.png
new file mode 100644
index 0000000000..dd782e3168
Binary files /dev/null and b/assets/images/help/pull_requests/dependency-review-rich-diff.png differ
diff --git a/assets/images/help/pull_requests/dependency-review-source-diff.png b/assets/images/help/pull_requests/dependency-review-source-diff.png
new file mode 100644
index 0000000000..aa5e394d81
Binary files /dev/null and b/assets/images/help/pull_requests/dependency-review-source-diff.png differ
diff --git a/assets/images/help/pull_requests/dependency-review-vulnerability.png b/assets/images/help/pull_requests/dependency-review-vulnerability.png
new file mode 100644
index 0000000000..9e69c9d0d5
Binary files /dev/null and b/assets/images/help/pull_requests/dependency-review-vulnerability.png differ
diff --git a/assets/images/help/pull_requests/file-filter-menu-json.png b/assets/images/help/pull_requests/file-filter-menu-json.png
new file mode 100644
index 0000000000..f37475293b
Binary files /dev/null and b/assets/images/help/pull_requests/file-filter-menu-json.png differ
diff --git a/assets/images/help/pull_requests/pull-request-tabs-changed-files.png b/assets/images/help/pull_requests/pull-request-tabs-changed-files.png
index a5ca74c433..34f5f6fc16 100644
Binary files a/assets/images/help/pull_requests/pull-request-tabs-changed-files.png and b/assets/images/help/pull_requests/pull-request-tabs-changed-files.png differ
diff --git a/assets/images/help/repository/actions-delete-artifact-updated.png b/assets/images/help/repository/actions-delete-artifact-updated.png
new file mode 100644
index 0000000000..b84fc77bd4
Binary files /dev/null and b/assets/images/help/repository/actions-delete-artifact-updated.png differ
diff --git a/assets/images/help/repository/actions-failed-pester-test-updated.png b/assets/images/help/repository/actions-failed-pester-test-updated.png
new file mode 100644
index 0000000000..022a0a5370
Binary files /dev/null and b/assets/images/help/repository/actions-failed-pester-test-updated.png differ
diff --git a/assets/images/help/repository/artifact-drop-down-updated.png b/assets/images/help/repository/artifact-drop-down-updated.png
new file mode 100644
index 0000000000..6ddc426fec
Binary files /dev/null and b/assets/images/help/repository/artifact-drop-down-updated.png differ
diff --git a/assets/images/help/repository/cancel-check-suite-updated.png b/assets/images/help/repository/cancel-check-suite-updated.png
new file mode 100644
index 0000000000..533a2bcb5e
Binary files /dev/null and b/assets/images/help/repository/cancel-check-suite-updated.png differ
diff --git a/assets/images/help/repository/copy-link-button-updated-2.png b/assets/images/help/repository/copy-link-button-updated-2.png
new file mode 100644
index 0000000000..dfa0bab265
Binary files /dev/null and b/assets/images/help/repository/copy-link-button-updated-2.png differ
diff --git a/assets/images/help/repository/delete-all-logs-updated-2.png b/assets/images/help/repository/delete-all-logs-updated-2.png
new file mode 100644
index 0000000000..a902b47f60
Binary files /dev/null and b/assets/images/help/repository/delete-all-logs-updated-2.png differ
diff --git a/assets/images/help/repository/docker-action-workflow-run-updated.png b/assets/images/help/repository/docker-action-workflow-run-updated.png
new file mode 100644
index 0000000000..ae31b91911
Binary files /dev/null and b/assets/images/help/repository/docker-action-workflow-run-updated.png differ
diff --git a/assets/images/help/repository/download-logs-drop-down-updated-2.png b/assets/images/help/repository/download-logs-drop-down-updated-2.png
new file mode 100644
index 0000000000..128d392113
Binary files /dev/null and b/assets/images/help/repository/download-logs-drop-down-updated-2.png differ
diff --git a/assets/images/help/repository/in-progress-run.png b/assets/images/help/repository/in-progress-run.png
new file mode 100644
index 0000000000..0419dc4929
Binary files /dev/null and b/assets/images/help/repository/in-progress-run.png differ
diff --git a/assets/images/help/repository/javascript-action-workflow-run-updated-2.png b/assets/images/help/repository/javascript-action-workflow-run-updated-2.png
new file mode 100644
index 0000000000..b60a7536fd
Binary files /dev/null and b/assets/images/help/repository/javascript-action-workflow-run-updated-2.png differ
diff --git a/assets/images/help/repository/passing-data-between-jobs-in-a-workflow-updated.png b/assets/images/help/repository/passing-data-between-jobs-in-a-workflow-updated.png
new file mode 100644
index 0000000000..c498028024
Binary files /dev/null and b/assets/images/help/repository/passing-data-between-jobs-in-a-workflow-updated.png differ
diff --git a/assets/images/help/repository/rerun-checks-drop-down-updated.png b/assets/images/help/repository/rerun-checks-drop-down-updated.png
new file mode 100644
index 0000000000..4d769074ee
Binary files /dev/null and b/assets/images/help/repository/rerun-checks-drop-down-updated.png differ
diff --git a/assets/images/help/repository/search-log-box-updated-2.png b/assets/images/help/repository/search-log-box-updated-2.png
new file mode 100644
index 0000000000..9ab228e44c
Binary files /dev/null and b/assets/images/help/repository/search-log-box-updated-2.png differ
diff --git a/assets/images/help/repository/super-linter-workflow-results-updated-2.png b/assets/images/help/repository/super-linter-workflow-results-updated-2.png
new file mode 100644
index 0000000000..6bdde69b11
Binary files /dev/null and b/assets/images/help/repository/super-linter-workflow-results-updated-2.png differ
diff --git a/assets/images/help/repository/superlinter-lint-code-base-job-updated.png b/assets/images/help/repository/superlinter-lint-code-base-job-updated.png
new file mode 100644
index 0000000000..80ee94d0ce
Binary files /dev/null and b/assets/images/help/repository/superlinter-lint-code-base-job-updated.png differ
diff --git a/assets/images/help/repository/upload-build-test-artifact.png b/assets/images/help/repository/upload-build-test-artifact.png
deleted file mode 100644
index 8739bf1192..0000000000
Binary files a/assets/images/help/repository/upload-build-test-artifact.png and /dev/null differ
diff --git a/assets/images/help/repository/view-run-billable-time.png b/assets/images/help/repository/view-run-billable-time.png
index 9f1b8643f9..c627f398b3 100644
Binary files a/assets/images/help/repository/view-run-billable-time.png and b/assets/images/help/repository/view-run-billable-time.png differ
diff --git a/assets/images/help/repository/workflow-run-kebab-horizontal-icon-updated-2.png b/assets/images/help/repository/workflow-run-kebab-horizontal-icon-updated-2.png
new file mode 100644
index 0000000000..952e86f576
Binary files /dev/null and b/assets/images/help/repository/workflow-run-kebab-horizontal-icon-updated-2.png differ
diff --git a/assets/images/help/settings/appearance-tab.png b/assets/images/help/settings/appearance-tab.png
index 5e5e975891..dcd3422ac6 100644
Binary files a/assets/images/help/settings/appearance-tab.png and b/assets/images/help/settings/appearance-tab.png differ
diff --git a/assets/images/help/settings/theme-settings-radio-buttons.png b/assets/images/help/settings/theme-settings-radio-buttons.png
new file mode 100644
index 0000000000..32c87156c7
Binary files /dev/null and b/assets/images/help/settings/theme-settings-radio-buttons.png differ
diff --git a/assets/images/help/settings/update-theme-preference-button.png b/assets/images/help/settings/update-theme-preference-button.png
new file mode 100644
index 0000000000..19fd375fe1
Binary files /dev/null and b/assets/images/help/settings/update-theme-preference-button.png differ
diff --git a/assets/images/help/sponsors/billing-account-switcher.png b/assets/images/help/sponsors/billing-account-switcher.png
new file mode 100644
index 0000000000..d3240f358a
Binary files /dev/null and b/assets/images/help/sponsors/billing-account-switcher.png differ
diff --git a/assets/images/help/sponsors/edit-sponsorship-payment-button.png b/assets/images/help/sponsors/edit-sponsorship-payment-button.png
index 36d2c51e49..369165c851 100644
Binary files a/assets/images/help/sponsors/edit-sponsorship-payment-button.png and b/assets/images/help/sponsors/edit-sponsorship-payment-button.png differ
diff --git a/assets/images/help/sponsors/link-account-button.png b/assets/images/help/sponsors/link-account-button.png
new file mode 100644
index 0000000000..c8d6992730
Binary files /dev/null and b/assets/images/help/sponsors/link-account-button.png differ
diff --git a/assets/images/help/sponsors/manage-your-sponsorship-button.png b/assets/images/help/sponsors/manage-your-sponsorship-button.png
index 3c094ea2fc..d6a7ad78bc 100644
Binary files a/assets/images/help/sponsors/manage-your-sponsorship-button.png and b/assets/images/help/sponsors/manage-your-sponsorship-button.png differ
diff --git a/assets/images/help/sponsors/organization-update-email-textbox.png b/assets/images/help/sponsors/organization-update-email-textbox.png
new file mode 100644
index 0000000000..2e5c3fdfc1
Binary files /dev/null and b/assets/images/help/sponsors/organization-update-email-textbox.png differ
diff --git a/assets/images/help/sponsors/pay-prorated-amount-link.png b/assets/images/help/sponsors/pay-prorated-amount-link.png
new file mode 100644
index 0000000000..6d73469e51
Binary files /dev/null and b/assets/images/help/sponsors/pay-prorated-amount-link.png differ
diff --git a/assets/images/help/sponsors/select-an-account-drop-down.png b/assets/images/help/sponsors/select-an-account-drop-down.png
new file mode 100644
index 0000000000..8cbe763af7
Binary files /dev/null and b/assets/images/help/sponsors/select-an-account-drop-down.png differ
diff --git a/assets/images/help/sponsors/sponsor-as-drop-down-menu.png b/assets/images/help/sponsors/sponsor-as-drop-down-menu.png
new file mode 100644
index 0000000000..0f9419d47f
Binary files /dev/null and b/assets/images/help/sponsors/sponsor-as-drop-down-menu.png differ
diff --git a/assets/images/help/sponsors/sponsoring-as-drop-down-menu.png b/assets/images/help/sponsors/sponsoring-as-drop-down-menu.png
new file mode 100644
index 0000000000..1ee7126df3
Binary files /dev/null and b/assets/images/help/sponsors/sponsoring-as-drop-down-menu.png differ
diff --git a/assets/images/help/sponsors/sponsoring-settings-button.png b/assets/images/help/sponsors/sponsoring-settings-button.png
new file mode 100644
index 0000000000..ce20577af9
Binary files /dev/null and b/assets/images/help/sponsors/sponsoring-settings-button.png differ
diff --git a/assets/images/help/sponsors/sponsoring-tab.png b/assets/images/help/sponsors/sponsoring-tab.png
new file mode 100644
index 0000000000..0c62587deb
Binary files /dev/null and b/assets/images/help/sponsors/sponsoring-tab.png differ
diff --git a/assets/images/help/sponsors/update-checkbox-manage.png b/assets/images/help/sponsors/update-checkbox-manage.png
new file mode 100644
index 0000000000..8fe85c0aa9
Binary files /dev/null and b/assets/images/help/sponsors/update-checkbox-manage.png differ
diff --git a/assets/images/marketplace/marketplace-request-button.png b/assets/images/marketplace/marketplace-request-button.png
index 5d20741085..f8152f0de5 100644
Binary files a/assets/images/marketplace/marketplace-request-button.png and b/assets/images/marketplace/marketplace-request-button.png differ
diff --git a/assets/images/marketplace/marketplace_verified_creator_badges_apps.png b/assets/images/marketplace/marketplace_verified_creator_badges_apps.png
new file mode 100644
index 0000000000..6f353f0adf
Binary files /dev/null and b/assets/images/marketplace/marketplace_verified_creator_badges_apps.png differ
diff --git a/content/actions/creating-actions/creating-a-docker-container-action.md b/content/actions/creating-actions/creating-a-docker-container-action.md
index a51ab9b2ca..ba06a3a938 100644
--- a/content/actions/creating-actions/creating-a-docker-container-action.md
+++ b/content/actions/creating-actions/creating-a-docker-container-action.md
@@ -226,6 +226,10 @@ jobs:
```
{% endraw %}
-From your repository, click the **Actions** tab, and select the latest workflow run. You should see "Hello Mona the Octocat" or the name you used for the `who-to-greet` input and the timestamp printed in the log.
+From your repository, click the **Actions** tab, and select the latest workflow run. {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}Under **Jobs** or in the visualization graph, click **A job to say hello**. {% endif %}You should see "Hello Mona the Octocat" or the name you used for the `who-to-greet` input and the timestamp printed in the log.
+{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+
+{% else %}

+{% endif %}
diff --git a/content/actions/creating-actions/creating-a-javascript-action.md b/content/actions/creating-actions/creating-a-javascript-action.md
index e2987d0d15..d738d7cd43 100644
--- a/content/actions/creating-actions/creating-a-javascript-action.md
+++ b/content/actions/creating-actions/creating-a-javascript-action.md
@@ -261,9 +261,11 @@ jobs:
```
{% endraw %}
-From your repository, click the **Actions** tab, and select the latest workflow run. You should see "Hello Mona the Octocat" or the name you used for the `who-to-greet` input and the timestamp printed in the log.
+From your repository, click the **Actions** tab, and select the latest workflow run. {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}Under **Jobs** or in the visualization graph, click **A job to say hello**. {% endif %}You should see "Hello Mona the Octocat" or the name you used for the `who-to-greet` input and the timestamp printed in the log.
-{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" %}
+{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+
+{% elsif currentVersion ver_gt "enterprise-server@2.22" %}

{% else %}

diff --git a/content/actions/guides/about-packaging-with-github-actions.md b/content/actions/guides/about-packaging-with-github-actions.md
index 020c8e636f..aba20c17f6 100644
--- a/content/actions/guides/about-packaging-with-github-actions.md
+++ b/content/actions/guides/about-packaging-with-github-actions.md
@@ -25,7 +25,11 @@ Creating a package at the end of a continuous integration workflow can help duri
Now, when reviewing a pull request, you'll be able to look at the workflow run and download the artifact that was produced.
+{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+
+{% else %}

+{% endif %}
This will let you run the code in the pull request on your machine, which can help with debugging or testing the pull request.
diff --git a/content/actions/guides/building-and-testing-powershell.md b/content/actions/guides/building-and-testing-powershell.md
index ccfa14b990..be50ffc52f 100644
--- a/content/actions/guides/building-and-testing-powershell.md
+++ b/content/actions/guides/building-and-testing-powershell.md
@@ -60,7 +60,11 @@ jobs:
* `run: Test-Path resultsfile.log` - Check whether a file called `resultsfile.log` is present in the repository's root directory.
* `Should -Be $true` - Uses Pester to define an expected result. If the result is unexpected, then {% data variables.product.prodname_actions %} flags this as a failed test. For example:
+ {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+ 
+ {% else %}

+ {% endif %}
* `Invoke-Pester Unit.Tests.ps1 -Passthru` - Uses Pester to execute tests defined in a file called `Unit.Tests.ps1`. For example, to perform the same test described above, the `Unit.Tests.ps1` will contain the following:
```
diff --git a/content/actions/guides/storing-workflow-data-as-artifacts.md b/content/actions/guides/storing-workflow-data-as-artifacts.md
index 5b8e0e7189..cf0932f879 100644
--- a/content/actions/guides/storing-workflow-data-as-artifacts.md
+++ b/content/actions/guides/storing-workflow-data-as-artifacts.md
@@ -108,8 +108,6 @@ jobs:
path: output/test/code-coverage.html
```
-
-
{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" %}
### Configuring a custom artifact retention period
@@ -238,7 +236,12 @@ jobs:
echo The result is $value
```
+The workflow run will archive any artifacts that it generated. For more information on downloading archived artifacts, see "[Downloading workflow artifacts](/actions/managing-workflow-runs/downloading-workflow-artifacts)."
+{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+
+{% else %}

+{% endif %}
{% if currentVersion == "free-pro-team@latest" %}
diff --git a/content/actions/learn-github-actions/introduction-to-github-actions.md b/content/actions/learn-github-actions/introduction-to-github-actions.md
index eecfa6589a..455cac19cf 100644
--- a/content/actions/learn-github-actions/introduction-to-github-actions.md
+++ b/content/actions/learn-github-actions/introduction-to-github-actions.md
@@ -204,7 +204,7 @@ In this diagram, you can see the workflow file you just created and how the {% d
### Viewing the job's activity
-Once your job has started running, you can view each step's activity on {% data variables.product.prodname_dotcom %}.
+Once your job has started running, you can {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}see a visualization graph of the run's progress and {% endif %}view each step's activity on {% data variables.product.prodname_dotcom %}.
{% data reusables.repositories.navigate-to-repo %}
1. Under your repository name, click **Actions**.
@@ -213,7 +213,14 @@ Once your job has started running, you can view each step's activity on {% data

1. Under "Workflow runs", click the name of the run you want to see.

-{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" %}
+{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+1. Under **Jobs** or in the visualization graph, click the job you want to see.
+ 
+{% endif %}
+{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+1. View the results of each step.
+ 
+{% elsif currentVersion ver_gt "enterprise-server@2.22" %}
1. Click on the job name to see the results of each step.

{% else %}
diff --git a/content/actions/managing-workflow-runs/canceling-a-workflow.md b/content/actions/managing-workflow-runs/canceling-a-workflow.md
index 20b9ba1cb4..153659d966 100644
--- a/content/actions/managing-workflow-runs/canceling-a-workflow.md
+++ b/content/actions/managing-workflow-runs/canceling-a-workflow.md
@@ -17,9 +17,14 @@ versions:
{% data reusables.repositories.navigate-to-repo %}
{% data reusables.repositories.actions-tab %}
{% data reusables.repositories.navigate-to-workflow %}
-{% data reusables.repositories.view-run %}
+1. From the list of workflow runs, click the name of the `queued` or `in progress` run that you want to cancel.
+
1. In the upper-right corner of the workflow, click **Cancel workflow**.
+{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+ 
+{% else %}

+{% endif %}
### Steps {% data variables.product.prodname_dotcom %} takes to cancel a workflow run
diff --git a/content/actions/managing-workflow-runs/downloading-workflow-artifacts.md b/content/actions/managing-workflow-runs/downloading-workflow-artifacts.md
index 22d5651efe..11843c3cec 100644
--- a/content/actions/managing-workflow-runs/downloading-workflow-artifacts.md
+++ b/content/actions/managing-workflow-runs/downloading-workflow-artifacts.md
@@ -20,4 +20,8 @@ versions:
{% data reusables.repositories.navigate-to-workflow %}
{% data reusables.repositories.view-run %}
1. Under **Artifacts**, click the artifact you want to download.
+ {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+ 
+ {% else %}

+ {% endif %}
diff --git a/content/actions/managing-workflow-runs/index.md b/content/actions/managing-workflow-runs/index.md
index 5096b761c7..8905d8b087 100644
--- a/content/actions/managing-workflow-runs/index.md
+++ b/content/actions/managing-workflow-runs/index.md
@@ -18,6 +18,7 @@ versions:
{% data reusables.actions.enterprise-beta %}
{% data reusables.actions.enterprise-github-hosted-runners %}
+{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}{% link_in_list /using-the-visualization-graph %}{% endif %}
{% link_in_list /viewing-workflow-run-history %}
{% link_in_list /using-workflow-run-logs %}
{% link_in_list /manually-running-a-workflow %}
diff --git a/content/actions/managing-workflow-runs/re-running-a-workflow.md b/content/actions/managing-workflow-runs/re-running-a-workflow.md
index 61590387db..323ebab2d3 100644
--- a/content/actions/managing-workflow-runs/re-running-a-workflow.md
+++ b/content/actions/managing-workflow-runs/re-running-a-workflow.md
@@ -16,5 +16,4 @@ versions:
{% data reusables.repositories.actions-tab %}
{% data reusables.repositories.navigate-to-workflow %}
{% data reusables.repositories.view-run %}
-1. In the upper-right corner of the workflow, use the **Re-run jobs** drop-down menu, and select **Re-run all jobs**.
- 
+1. In the upper-right corner of the workflow, use the **Re-run jobs** drop-down menu, and select **Re-run all jobs**.{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}{% else %}{% endif %}
diff --git a/content/actions/managing-workflow-runs/removing-workflow-artifacts.md b/content/actions/managing-workflow-runs/removing-workflow-artifacts.md
index 9c7e96fe79..33595a42cf 100644
--- a/content/actions/managing-workflow-runs/removing-workflow-artifacts.md
+++ b/content/actions/managing-workflow-runs/removing-workflow-artifacts.md
@@ -27,7 +27,11 @@ versions:
{% data reusables.repositories.navigate-to-workflow %}
{% data reusables.repositories.view-run %}
1. Under **Artifacts**, click {% octicon "trashcan" aria-label="The trashcan icon" %} next to the artifact you want to remove.
+ {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+ 
+ {% else %}

+ {% endif %}
{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" %}
### Setting the retention period for an artifact
diff --git a/content/actions/managing-workflow-runs/using-the-visualization-graph.md b/content/actions/managing-workflow-runs/using-the-visualization-graph.md
new file mode 100644
index 0000000000..2fe6753a30
--- /dev/null
+++ b/content/actions/managing-workflow-runs/using-the-visualization-graph.md
@@ -0,0 +1,23 @@
+---
+title: Using the visualization graph
+intro: Every workflow run generates a real-time graph that illustrates the run progress. You can use this graph to monitor and debug workflows.
+product: '{% data reusables.gated-features.actions %}'
+versions:
+ free-pro-team: '*'
+ enterprise-server: '>=3.1'
+---
+
+{% data reusables.actions.enterprise-beta %}
+{% data reusables.actions.visualization-beta %}
+{% data reusables.actions.enterprise-github-hosted-runners %}
+
+{% data reusables.repositories.navigate-to-repo %}
+{% data reusables.repositories.actions-tab %}
+{% data reusables.repositories.navigate-to-workflow %}
+{% data reusables.repositories.view-run %}
+
+1. The graph displays each job in the workflow. An icon to the left of the job name indicates the status of the job. Lines between jobs indicate dependencies.
+ 
+
+2. Click on a job to view the job log.
+ 
diff --git a/content/actions/managing-workflow-runs/using-workflow-run-logs.md b/content/actions/managing-workflow-runs/using-workflow-run-logs.md
index 92ad242d9c..9cffb45bc3 100644
--- a/content/actions/managing-workflow-runs/using-workflow-run-logs.md
+++ b/content/actions/managing-workflow-runs/using-workflow-run-logs.md
@@ -45,7 +45,11 @@ You can search the build logs for a particular step. When you search logs, only
{% data reusables.repositories.navigate-to-job-superlinter %}
{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" %}
1. In the upper-right corner of the log output, in the **Search logs** search box, type a search query.
+{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+ 
+{% else %}

+{% endif %}
{% else %}
1. To expand each step you want to include in your search, click the step.

@@ -63,8 +67,12 @@ You can download the log files from your workflow run. You can also download a w
{% data reusables.repositories.view-run-superlinter %}
{% data reusables.repositories.navigate-to-job-superlinter %}
{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" %}
-1. In the upper right corner, click {% octicon "kebab-horizontal" aria-label="The horizontal kebab icon" %} and select **Download log archive**.
+1. In the upper right corner, click {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}{% octicon "gear" aria-label="The gear icon" %}{% else %}{% octicon "kebab-horizontal" aria-label="The horizontal kebab icon" %}{% endif %} and select **Download log archive**.
+ {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+ 
+ {% else %}

+ {% endif %}
{% else %}
1. In the upper right corner, click {% octicon "kebab-horizontal" aria-label="The horizontal kebab icon" %} and select **Download log archive**.

@@ -80,9 +88,17 @@ You can delete the log files from your workflow run. {% data reusables.repositor
{% data reusables.repositories.view-run-superlinter %}
{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@2.22" %}
1. In the upper right corner, click {% octicon "kebab-horizontal" aria-label="The horizontal kebab icon" %}.
+ {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+ 
+ {% else %}

+ {% endif %}
2. To delete the log files, click the **Delete all logs** button and review the confirmation prompt.
+ {% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+ 
+ {% else %}

+ {% endif %}
After deleting logs, the **Delete all logs** button is removed to indicate that no log files remain in the workflow run.
{% else %}
1. In the upper right corner, click {% octicon "kebab-horizontal" aria-label="The horizontal kebab icon" %}.
diff --git a/content/actions/managing-workflow-runs/viewing-job-execution-time.md b/content/actions/managing-workflow-runs/viewing-job-execution-time.md
index 5b4d222793..e1b7bd9ac0 100644
--- a/content/actions/managing-workflow-runs/viewing-job-execution-time.md
+++ b/content/actions/managing-workflow-runs/viewing-job-execution-time.md
@@ -15,7 +15,7 @@ Billable job execution minutes are only shown for jobs run on private repositori
{% data reusables.repositories.actions-tab %}
{% data reusables.repositories.navigate-to-workflow %}
{% data reusables.repositories.view-run %}
-1. Under the job summary, you can view the job's execution time. To view the billable job execution time, click **Run and billable time details**.
+1. Under the job summary, you can view the job's execution time. To view details about the billable job execution time, click the time under **Billable time**.

{% note %}
diff --git a/content/actions/quickstart.md b/content/actions/quickstart.md
index 77e8752480..812f317f03 100644
--- a/content/actions/quickstart.md
+++ b/content/actions/quickstart.md
@@ -60,8 +60,13 @@ Committing the workflow file in your repository triggers the `push` event and ru
{% data reusables.repositories.actions-tab %}
{% data reusables.repositories.navigate-to-workflow-superlinter %}
{% data reusables.repositories.view-run-superlinter %}
+{% if currentVersion == "free-pro-team@latest" or currentVersion ver_gt "enterprise-server@3.0" %}
+1. Under **Jobs** or in the visualization graph, click the **Lint code base** job.
+ 
+{% else %}
1. In the left sidebar, click the **Lint code base** job.

+{% endif %}
{% data reusables.repositories.view-failed-job-results-superlinter %}
### More starter workflows
diff --git a/content/developers/github-marketplace/about-github-marketplace.md b/content/developers/github-marketplace/about-github-marketplace.md
index 55cab928e0..221d2e6447 100644
--- a/content/developers/github-marketplace/about-github-marketplace.md
+++ b/content/developers/github-marketplace/about-github-marketplace.md
@@ -1,6 +1,6 @@
---
title: About GitHub Marketplace
-intro: 'Learn the basics to prepare your app for review before joining {% data variables.product.prodname_marketplace %}.'
+intro: 'Learn about {% data variables.product.prodname_marketplace %} where you can share your apps and actions publicly with all {% data variables.product.product_name %} users.'
redirect_from:
- /apps/marketplace/getting-started/
- /marketplace/getting-started
@@ -14,52 +14,41 @@ versions:
{% data reusables.actions.actions-not-verified %}
-To learn about publishing {% data variables.product.prodname_actions %} in the {% data variables.product.prodname_marketplace %}, see "[Publishing actions in GitHub Marketplace](/actions/creating-actions/publishing-actions-in-github-marketplace)."
+To learn about publishing {% data variables.product.prodname_actions %} in {% data variables.product.prodname_marketplace %}, see "[Publishing actions in GitHub Marketplace](/actions/creating-actions/publishing-actions-in-github-marketplace)."
### Apps
-You can list verified and unverified apps in {% data variables.product.prodname_marketplace %}. Unverified apps do not go through the security, testing, and verification cycle {% data variables.product.prodname_dotcom %} requires for verified apps.
+Anyone can share their apps with other users on {% data variables.product.prodname_marketplace %} but only listings that are verified by {% data variables.product.company_short %} can include paid plans. For more information, see "[About verified creators](/developers/github-marketplace/about-verified-creators)."
-Verified apps have a green badge in {% data variables.product.prodname_marketplace %}. Unverified apps have a grey badge next to their listing and are only available as free apps.
+If you're interested in creating an app for {% data variables.product.prodname_marketplace %}, but you're new to {% data variables.product.prodname_github_apps %} or {% data variables.product.prodname_oauth_app %}s, see "[Building {% data variables.product.prodname_github_apps %}](/developers/apps/building-github-apps)" or "[Building {% data variables.product.prodname_oauth_app %}s](/developers/apps/building-oauth-apps)."
-
-
-If you're interested in creating an app for {% data variables.product.prodname_marketplace %}, but you're new to {% data variables.product.prodname_github_apps %} and {% data variables.product.prodname_oauth_app %}s, see "[Building apps](/apps/)."
-
-{% data reusables.marketplace.github_apps_preferred %}, although you can list both OAuth and {% data variables.product.prodname_github_app %}s in {% data variables.product.prodname_marketplace %}. See "[Differences between GitHub and OAuth apps](/apps/differences-between-apps/)" for more details. To learn more about switching from OAuth to {% data variables.product.prodname_github_apps %}, see [Migrating OAuth Apps to {% data variables.product.prodname_github_app %}s](/apps/migrating-oauth-apps-to-github-apps/).
+{% data reusables.marketplace.github_apps_preferred %}, although you can list both OAuth and {% data variables.product.prodname_github_app %}s in {% data variables.product.prodname_marketplace %}. For more information, see "[Differences between {% data variables.product.prodname_github_apps %} and {% data variables.product.prodname_oauth_app %}s](/apps/differences-between-apps/)" and "[Migrating {% data variables.product.prodname_oauth_app %}s to {% data variables.product.prodname_github_apps %}](/apps/migrating-oauth-apps-to-github-apps/)."
If you have questions about {% data variables.product.prodname_marketplace %}, please contact {% data variables.contact.contact_support %} directly.
-#### Unverified Apps
+### Publishing an app to {% data variables.product.prodname_marketplace %}
-Unverified apps do not need to meet the "[Requirements for listing an app on {% data variables.product.prodname_marketplace %}](/marketplace/getting-started/requirements-for-listing-an-app-on-github-marketplace/)" or go through the "[Security review process](/marketplace/getting-started/security-review-process/)".
+When you have finished creating your app, you can share it with other users by publishing it to {% data variables.product.prodname_marketplace %}. In summary, the process is:
-{% data reusables.marketplace.unverified-apps %} Having a published paid plan will prevent you from being able to submit an unverified app. You must remove paid plans or keep them in draft mode before publishing an unverified app.
+1. Review your app carefully to ensure that it will behave as expected in other repositories and that it follows best practice guidelines. For more information, see "[Security best practices for apps](/developers/github-marketplace/security-best-practices-for-apps)" and "[Requirements for listing an app](/developers/github-marketplace/requirements-for-listing-an-app#best-practice-for-customer-experience)."
-To list your unverified app in {% data variables.product.prodname_marketplace %}, you only need to create a "[Listing on {% data variables.product.prodname_marketplace %}](/marketplace/listing-on-github-marketplace/)" and submit it as an unverified listing.
+1. Add webhook events to the app to track user billing requests. For more information about the {% data variables.product.prodname_marketplace %} API, webhook events, and billing requests, see "[Using the {% data variables.product.prodname_marketplace %} API in your app](/developers/github-marketplace/using-the-github-marketplace-api-in-your-app)."
-{% data reusables.marketplace.launch-with-free %}
+1. Create a draft {% data variables.product.prodname_marketplace %} listing. For more information, see "[Drafting a listing for your app](/developers/github-marketplace/drafting-a-listing-for-your-app)."
-#### Verified Apps
+1. Add a pricing plan. For more information, see "[Setting pricing plans for your listing](/developers/github-marketplace/setting-pricing-plans-for-your-listing)."
-If you've already built an app and you're interested in submitting a verified listing in {% data variables.product.prodname_marketplace %}, start here:
+1. Check whether your app meets the requirements for listing on {% data variables.product.prodname_marketplace %} as a free or a paid app. For more information, see "[Requirements for listing an app](/developers/github-marketplace/requirements-for-listing-an-app)."
-1. [Getting started with {% data variables.product.prodname_marketplace %}](/marketplace/getting-started/)
Learn about requirements, guidelines, and the app submission process.
+1. Read and accept the terms of the "[{% data variables.product.prodname_marketplace %} Developer Agreement](/articles/github-marketplace-developer-agreement/)."
-1. [Integrating with the {% data variables.product.prodname_marketplace %} API](/marketplace/integrating-with-the-github-marketplace-api/)
Before you can list your app on {% data variables.product.prodname_marketplace %}, you'll need to integrate billing flows using the {% data variables.product.prodname_marketplace %} API and webhook events.
+1. Submit your listing for publication in {% data variables.product.prodname_marketplace %}, requesting verification if you want to sell the app. For more information, see "[Submitting your listing for publication](/developers/github-marketplace/submitting-your-listing-for-publication)."
-1. [Listing on {% data variables.product.prodname_marketplace %}](/marketplace/listing-on-github-marketplace/)
Create a draft {% data variables.product.prodname_marketplace %} listing, configure webhook settings, and set up pricing plans.
+An onboarding expert will contact you with any questions or further steps. For example, if you have added a paid plan, you will need to complete the verification process and complete financial onboarding. As soon as your listing is approved the app is published to {% data variables.product.prodname_marketplace %}.
-1. [Selling your app](/marketplace/selling-your-app/)
Learn about pricing plans, billing cycles, and how to receive payment from {% data variables.product.prodname_dotcom %} for your app.
+### Seeing how your app is performing
-1. [{% data variables.product.prodname_marketplace %} Insights](/marketplace/github-marketplace-insights/)
See how your app is performing in {% data variables.product.prodname_marketplace %}. You can use metrics collected by {% data variables.product.prodname_dotcom %} to guide your marketing campaign and be successful in {% data variables.product.prodname_marketplace %}.
+You can access metrics and transactions for your listing. For more information, see:
-1. [{% data variables.product.prodname_marketplace %} transactions](/marketplace/github-marketplace-transactions/)
Download and view transaction data for your {% data variables.product.prodname_marketplace %} listing.
-
-### Reviewing your app
-
-We want to make sure that the apps offered on {% data variables.product.prodname_marketplace %} are safe, secure, and well tested. The {% data variables.product.prodname_marketplace %} onboarding specialists will review your app to ensure that it meets all requirements. Follow the guidelines in these articles before submitting your app:
-
-
-* [Requirements for listing an app on {% data variables.product.prodname_marketplace %}](/marketplace/getting-started/requirements-for-listing-an-app-on-github-marketplace/)
-* [Security review process](/marketplace/getting-started/security-review-process/)
+- "[Viewing metrics for your listing](/developers/github-marketplace/viewing-metrics-for-your-listing)"
+- "[Viewing transactions for your listing](/developers/github-marketplace/viewing-transactions-for-your-listing)"
diff --git a/content/developers/github-marketplace/about-verified-creators.md b/content/developers/github-marketplace/about-verified-creators.md
new file mode 100644
index 0000000000..a3c0eeb880
--- /dev/null
+++ b/content/developers/github-marketplace/about-verified-creators.md
@@ -0,0 +1,43 @@
+---
+title: About verified creators
+intro: 'Each organization that wants to sell apps on {% data variables.product.prodname_marketplace %} must follow a verification process. Their identity is checked and their billing process reviewed.'
+versions:
+ free-pro-team: '*'
+---
+
+### About verified creators
+
+A verified creator is an organization that {% data variables.product.company_short %} has checked. Anyone can share their apps with other users on {% data variables.product.prodname_marketplace %} but only organizations that are verified by {% data variables.product.company_short %} can sell apps. For more information about organizations, see "[About organizations](/github/setting-up-and-managing-organizations-and-teams/about-organizations)."
+
+The verification process aims to protect users. For example, it verifies the seller's identity, checks that their {% data variables.product.product_name %} organization is set up securely, and that they can be contacted for support.
+
+After passing the verification checks, any apps that the organization lists on {% data variables.product.prodname_marketplace %} are shown with a verified creator badge {% octicon "verified" aria-label="Verified creator badge" %}. The organization can now add paid plans to any of their apps. Each app with a paid plan also goes through a financial onboarding process to check that it's set up to handle billing correctly.
+
+
+
+In addition to the verified creator badge, you'll also see badges for unverified and verified apps. These apps were published using the old method for verifying individual apps.
+
+
+
+For information on finding apps to use, see "[Searching {% data variables.product.prodname_marketplace %}](/github/searching-for-information-on-github/searching-github-marketplace)."
+
+### About the verification process
+
+The first time you request verification for a listing of one of your apps, you will enter the verification process. An onboarding expert will guide you through the process. This includes checking:
+
+- Profile information - The basic profile information is populated accurately and appropriately.
+- Security - The organization has enabled two-factor authentication.
+- Verified domain - The organization has verified the domain of the site URL.
+- Purchase webhook event - The event is handled correctly by the app.
+
+When your organization is verified, all your apps are shown with a verified creator badge. You are now able to offer paid plans for any of your apps.
+
+For more information about the requirements for listing an app on {% data variables.product.prodname_marketplace %}, see "[Requirements for listing an app on {% data variables.product.prodname_marketplace %}](/marketplace/getting-started/requirements-for-listing-an-app-on-github-marketplace/)."
+
+{% data reusables.marketplace.app-transfer-to-org-for-verification %} For information on how to do this, see: "[Submitting your listing for publication](/developers/github-marketplace/submitting-your-listing-for-publication#transferring-an-app-to-an-organization-before-you-submit)."
+
+{% note %}
+
+**Note:** This verification process for apps replaces the previous process where individual apps were verified. The current process is similar to the verification process for actions. If you have apps that were verified under the old process, these will not be affected by the changes. The {% data variables.product.prodname_marketplace %} team will contact you with details of how to migrate to organization-based verification.
+
+{% endnote %}
diff --git a/content/developers/github-marketplace/billing-customers.md b/content/developers/github-marketplace/billing-customers.md
index 940892862b..ed6701d671 100644
--- a/content/developers/github-marketplace/billing-customers.md
+++ b/content/developers/github-marketplace/billing-customers.md
@@ -13,17 +13,17 @@ versions:
### Understanding the billing cycle
-Customers can choose a monthly or yearly billing cycle when they purchase your app. All changes customers make to the billing cycle and plan selection will trigger a `marketplace_purchase` event. You can refer to the `marketplace_purchase` webhook payload to see which billing cycle a customer selects and when the next billing date begins (`effective_date`). For more information about webhook payloads, see "[{% data variables.product.prodname_marketplace %} webhook events](/marketplace/integrating-with-the-github-marketplace-api/github-marketplace-webhook-events/)."
+Customers can choose a monthly or yearly billing cycle when they purchase your app. All changes customers make to the billing cycle and plan selection will trigger a `marketplace_purchase` event. You can refer to the `marketplace_purchase` webhook payload to see which billing cycle a customer selects and when the next billing date begins (`effective_date`). For more information about webhook payloads, see "[Webhook events for the {% data variables.product.prodname_marketplace %} API](/developers/github-marketplace/webhook-events-for-the-github-marketplace-api)."
### Providing billing services in your app's UI
-Customers must be able to perform the following actions from your app's website:
-- Customers must be able to modify or cancel their {% data variables.product.prodname_marketplace %} plans for personal and organizational accounts separately.
+Customers should be able to perform the following actions from your app's website:
+- Customers should be able to modify or cancel their {% data variables.product.prodname_marketplace %} plans for personal and organizational accounts separately.
{% data reusables.marketplace.marketplace-billing-ui-requirements %}
### Billing services for upgrades, downgrades, and cancellations
-Follow these guidelines for upgrades, downgrades, and cancellations to maintain a clear and consistent billing process. For more detailed instructions about the {% data variables.product.prodname_marketplace %} purchase events, see "[Billing flows](/marketplace/integrating-with-the-github-marketplace-api/#billing-flows)."
+Follow these guidelines for upgrades, downgrades, and cancellations to maintain a clear and consistent billing process. For more detailed instructions about the {% data variables.product.prodname_marketplace %} purchase events, see "[Using the {% data variables.product.prodname_marketplace %} API in your app](/developers/github-marketplace/using-the-github-marketplace-api-in-your-app)."
You can use the `marketplace_purchase` webhook's `effective_date` key to determine when a plan change will occur and periodically synchronize the [List accounts for a plan](/rest/reference/apps#list-accounts-for-a-plan).
@@ -33,7 +33,7 @@ When a customer upgrades their pricing plan or changes their billing cycle from
{% data reusables.marketplace.marketplace-failed-purchase-event %}
-For information about building upgrade and downgrade workflows into your app, see "[Upgrading and downgrading plans](/marketplace/integrating-with-the-github-marketplace-api/upgrading-and-downgrading-plans/)."
+For information about building upgrade and downgrade workflows into your app, see "[Handling plan changes](/developers/github-marketplace/handling-plan-changes)."
#### Downgrades and cancellations
@@ -45,4 +45,4 @@ When a customer cancels a plan, you must:
{% data reusables.marketplace.cancellation-clarification %}
- Enable them to upgrade the plan through GitHub if they would like to continue the plan at a later time.
-For information about building cancellation workflows into your app, see "[Cancelling plans](/marketplace/integrating-with-the-github-marketplace-api/cancelling-plans/)."
+For information about building cancellation workflows into your app, see "[Handling plan cancellations](/developers/github-marketplace/handling-plan-cancellations)."
diff --git a/content/developers/github-marketplace/customer-experience-best-practices-for-apps.md b/content/developers/github-marketplace/customer-experience-best-practices-for-apps.md
new file mode 100644
index 0000000000..4107fa4427
--- /dev/null
+++ b/content/developers/github-marketplace/customer-experience-best-practices-for-apps.md
@@ -0,0 +1,20 @@
+---
+title: Customer experience best practices for apps
+intro: 'Guidelines for creating an app that will be easy to use and understand.'
+shortTitle: Customer experience best practice
+versions:
+ free-pro-team: '*'
+---
+
+If you follow these best practices it will help you to provide a good customer experience.
+
+### Customer communication
+
+- Marketing materials for the app should accurately represent the app's behavior.
+- Apps should include links to user-facing documentation that describe how to set up and use the app.
+- Customers should be able to see what type of plan they have in the billing, profile, or account settings section of the app.
+- Customers should be able to install and use your app on both a personal account and an organization account. They should be able to view and manage the app on those accounts separately.
+
+### Plan management
+
+{% data reusables.marketplace.marketplace-billing-ui-requirements %}
diff --git a/content/developers/github-marketplace/drafting-a-listing-for-your-app.md b/content/developers/github-marketplace/drafting-a-listing-for-your-app.md
index 2f3be55bcf..11247e591d 100644
--- a/content/developers/github-marketplace/drafting-a-listing-for-your-app.md
+++ b/content/developers/github-marketplace/drafting-a-listing-for-your-app.md
@@ -59,8 +59,8 @@ Once you've created a {% data variables.product.prodname_marketplace %} draft li
### Submitting your app
-Once you've completed your {% data variables.product.prodname_marketplace %} listing, you can submit your listing for review from the **Overview** page. You'll need to read and accept the "[{% data variables.product.prodname_marketplace %} Developer Agreement](/articles/github-marketplace-developer-agreement/)," and then you can click **Submit for review**. After you submit your app for review, the {% data variables.product.prodname_marketplace %} onboarding team will contact you with additional information about the onboarding process. You can learn more about the onboarding and security review process in "[Getting started with {% data variables.product.prodname_marketplace %}](/marketplace/getting-started/)."
+Once you've completed your {% data variables.product.prodname_marketplace %} listing, you can submit your listing for review from the **Overview** page. You'll need to read and accept the "[{% data variables.product.prodname_marketplace %} Developer Agreement](/articles/github-marketplace-developer-agreement/)," and then you can click **Submit for review**. After you submit your app for review, an onboarding expert will contact you with additional information about the onboarding process. You can learn more about the onboarding and security review process in "[Getting started with {% data variables.product.prodname_marketplace %}](/marketplace/getting-started/)."
### Removing a {% data variables.product.prodname_marketplace %} listing
-If you no longer want to list your app in {% data variables.product.prodname_marketplace %}, contact [marketplace@github.com](mailto:marketplace@github.com) to remove your listing.
+If you no longer want to list your app in {% data variables.product.prodname_marketplace %}, contact {% data variables.contact.contact_support %} to remove your listing.
diff --git a/content/developers/github-marketplace/handling-new-purchases-and-free-trials.md b/content/developers/github-marketplace/handling-new-purchases-and-free-trials.md
index 962262a477..0c67eec856 100644
--- a/content/developers/github-marketplace/handling-new-purchases-and-free-trials.md
+++ b/content/developers/github-marketplace/handling-new-purchases-and-free-trials.md
@@ -28,7 +28,7 @@ GitHub then sends the [`marketplace_purchase`](/webhooks/event-payloads/#marketp
Read the `effective_date` and `marketplace_purchase` object from the `marketplace_purchase` webhook to determine which plan the customer purchased, when the billing cycle starts, and when the next billing cycle begins.
-If your app offers a free trial, read the `marketplace_purchase[on_free_trial]` attribute from the webhook. If the value is `true`, your app will need to track the free trial start date (`effective_date`) and the date the free trial ends (`free_trial_ends_on`). Use the `free_trial_ends_on` date to display the remaining days left in a free trial in your app's UI. You can do this in either a banner or in your [billing UI](/marketplace/selling-your-app/billing-customers-in-github-marketplace/#providing-billing-services-in-your-apps-ui). To learn how to handle cancellations before a free trial ends, see "[Cancelling plans](/marketplace/integrating-with-the-github-marketplace-api/cancelling-plans/)." See "[Upgrading and downgrading plans](/marketplace/integrating-with-the-github-marketplace-api/upgrading-and-downgrading-plans/)" to find out how to transition a free trial to a paid plan when a free trial expires.
+If your app offers a free trial, read the `marketplace_purchase[on_free_trial]` attribute from the webhook. If the value is `true`, your app will need to track the free trial start date (`effective_date`) and the date the free trial ends (`free_trial_ends_on`). Use the `free_trial_ends_on` date to display the remaining days left in a free trial in your app's UI. You can do this in either a banner or in your [billing UI](/marketplace/selling-your-app/billing-customers-in-github-marketplace/#providing-billing-services-in-your-apps-ui). To learn how to handle cancellations before a free trial ends, see "[Handling plan cancellations](/developers/github-marketplace/handling-plan-cancellations)." See "[Handling plan changes](/developers/github-marketplace/handling-plan-changes)" to find out how to transition a free trial to a paid plan when a free trial expires.
See "[{% data variables.product.prodname_marketplace %} webhook events](/marketplace/integrating-with-the-github-marketplace-api/github-marketplace-webhook-events/)" for an example of the `marketplace_purchase` event payload.
diff --git a/content/developers/github-marketplace/index.md b/content/developers/github-marketplace/index.md
index 34b9741c61..0881424066 100644
--- a/content/developers/github-marketplace/index.md
+++ b/content/developers/github-marketplace/index.md
@@ -11,8 +11,10 @@ versions:
{% topic_link_in_list /creating-apps-for-github-marketplace %}
{% link_in_list /about-github-marketplace %}
+ {% link_in_list /about-verified-creators %}
{% link_in_list /requirements-for-listing-an-app %}
- {% link_in_list /security-review-process-for-submitted-apps %}
+ {% link_in_list /security-best-practices-for-apps %}
+ {% link_in_list /customer-experience-best-practices-for-apps %}
{% link_in_list /viewing-metrics-for-your-listing %}
{% link_in_list /viewing-transactions-for-your-listing %}
{% topic_link_in_list /using-the-github-marketplace-api-in-your-app %}
@@ -27,7 +29,7 @@ versions:
{% link_in_list /writing-a-listing-description-for-your-app %}
{% link_in_list /setting-pricing-plans-for-your-listing %}
{% link_in_list /configuring-a-webhook-to-notify-you-of-plan-changes %}
- {% link_in_list /submitting-your-listing-for-review %}
+ {% link_in_list /submitting-your-listing-for-publication %}
{% topic_link_in_list /selling-your-app-on-github-marketplace %}
{% link_in_list /pricing-plans-for-github-marketplace-apps %}
{% link_in_list /billing-customers %}
diff --git a/content/developers/github-marketplace/pricing-plans-for-github-marketplace-apps.md b/content/developers/github-marketplace/pricing-plans-for-github-marketplace-apps.md
index bb33fe004f..aa9d924b49 100644
--- a/content/developers/github-marketplace/pricing-plans-for-github-marketplace-apps.md
+++ b/content/developers/github-marketplace/pricing-plans-for-github-marketplace-apps.md
@@ -10,35 +10,45 @@ versions:
-{% data variables.product.prodname_marketplace %} pricing plans can be free, flat rate, or per-unit, and GitHub lists the price in US dollars. Customers purchase your app using a payment method attached to their {% data variables.product.product_name %} account, without having to leave GitHub.com. You don't have to write code to perform billing transactions, but you will have to handle [billing flows](/marketplace/integrating-with-the-github-marketplace-api/#billing-flows) for purchase events.
+{% data variables.product.prodname_marketplace %} pricing plans can be free, flat rate, or per-unit. Prices are set, displayed, and processed in US dollars. Paid plans are restricted to verified listings.
+
+Customers purchase your app using a payment method attached to their {% data variables.product.product_name %} account, without having to leave {% data variables.product.prodname_dotcom_the_website %}. You don't have to write code to perform billing transactions, but you will have to handle events from the {% data variables.product.prodname_marketplace %} API. For more information, see "[Using the {% data variables.product.prodname_marketplace %} API in your app](/developers/github-marketplace/using-the-github-marketplace-api-in-your-app)."
If the app you're listing on {% data variables.product.prodname_marketplace %} has multiple plan options, you can set up corresponding pricing plans. For example, if your app has two plan options, an open source plan and a pro plan, you can set up a free pricing plan for your open source plan and a flat pricing plan for your pro plan. Each {% data variables.product.prodname_marketplace %} listing must have an annual and a monthly price for every plan that's listed.
For more information on how to create a pricing plan, see "[Setting a {% data variables.product.prodname_marketplace %} listing's pricing plan](/marketplace/listing-on-github-marketplace/setting-a-github-marketplace-listing-s-pricing-plan/)."
-{% note %}
-
-**Note:** If you're listing an app on {% data variables.product.prodname_marketplace %}, you can't list your app with a free pricing plan if you offer a paid service outside of {% data variables.product.prodname_marketplace %}.
-
-{% endnote %}
+{% data reusables.marketplace.free-plan-note %}
### Types of pricing plans
-**Free pricing plans** are completely free for users. If you set up a free pricing plan, you cannot charge users that choose the free pricing plan for the use of your app. You can create both free and paid plans for your listing. Unverified free apps do not need to implement any billing flows. Free apps that are verified by Github need to implement billing flows for new purchases and cancellations, but do not need to implement billing flows for free trials, upgrades, and downgrades. If you add a paid plan to an app that you've already listed in {% data variables.product.prodname_marketplace %} as a free service, you'll need to resubmit the app for review.
+#### Free pricing plans
-**Flat rate pricing plans** charge a set fee on a monthly and yearly basis.
+{% data reusables.marketplace.free-apps-encouraged %}
-**Per-unit pricing plans** charge a set fee on either a monthly or yearly basis for a unit that you specify. A "unit" can be anything you'd like (for example, a user, seat, or person).
+Free plans are completely free for users. If you set up a free pricing plan, you cannot charge users that choose the free pricing plan for the use of your app. You can create both free and paid plans for your listing.
-**Marketplace free trials** provide 14-day free trials of OAuth or GitHub Apps to customers. When you [set up a Marketplace pricing plan](/marketplace/listing-on-github-marketplace/setting-a-github-marketplace-listing-s-pricing-plan/), you can select the option to provide a free trial for flat-rate or per-unit pricing plans.
+All apps need to handle events for new purchases and cancellations. Apps that only have free plans do not need to handle events for free trials, upgrades, and downgrades. For more information, see: "[Using the {% data variables.product.prodname_marketplace %} API in your app](/developers/github-marketplace/using-the-github-marketplace-api-in-your-app)."
+
+If you add a paid plan to an app that you've already listed in {% data variables.product.prodname_marketplace %} as a free service, you'll need to request verification for the app and go through financial onboarding.
+
+#### Paid pricing plans
+
+There are two types of paid pricing plan:
+
+- Flat rate pricing plans charge a set fee on a monthly and yearly basis.
+
+- Per-unit pricing plans charge a set fee on either a monthly or yearly basis for a unit that you specify. A "unit" can be anything you'd like (for example, a user, seat, or person).
+
+You may also want to offer free trials. These provide free, 14-day trials of OAuth or GitHub Apps to customers. When you set up a Marketplace pricing plan, you can select the option to provide a free trial for flat-rate or per-unit pricing plans.
### Free trials
-Customers can start a free trial for any available paid plan on a Marketplace listing, but will not be able to create more than one free trial for a Marketplace product.
+Customers can start a free trial for any paid plan on a Marketplace listing that includes free trials. However, customers cannot create more than one free trial per marketplace product.
Free trials have a fixed length of 14 days. Customers are notified 4 days before the end of their trial period (on day 11 of the free trial) that their plan will be upgraded. At the end of a free trial, customers will be auto-enrolled into the plan they are trialing if they do not cancel.
-See "[New purchases and free trials](/marketplace/integrating-with-the-github-marketplace-api/handling-new-purchases-and-free-trials/)" for details on how to handle free trials in your app.
+For more information, see: "[Handling new purchases and free trials](/developers/github-marketplace/integrating-with-the-github-marketplace-api/handling-new-purchases-and-free-trials/)."
{% note %}
diff --git a/content/developers/github-marketplace/requirements-for-listing-an-app.md b/content/developers/github-marketplace/requirements-for-listing-an-app.md
index ead5d811bb..e54424b74a 100644
--- a/content/developers/github-marketplace/requirements-for-listing-an-app.md
+++ b/content/developers/github-marketplace/requirements-for-listing-an-app.md
@@ -1,6 +1,6 @@
---
title: Requirements for listing an app
-intro: 'Apps on {% data variables.product.prodname_marketplace %} must meet the requirements outlined on this page before our {% data variables.product.prodname_marketplace %} onboarding specialists will approve the listing.'
+intro: 'Apps on {% data variables.product.prodname_marketplace %} must meet the requirements outlined on this page before the listing can be published.'
redirect_from:
- /apps/adding-integrations/listing-apps-on-github-marketplace/requirements-for-listing-an-app-on-github-marketplace/
- /apps/marketplace/listing-apps-on-github-marketplace/requirements-for-listing-an-app-on-github-marketplace/
@@ -12,49 +12,62 @@ versions:
free-pro-team: '*'
---
+
+The requirements for listing an app on {% data variables.product.prodname_marketplace %} vary according to whether you want to offer a free or a paid app.
-Before you submit your app for review, you must read and accept the terms of the "[{% data variables.product.prodname_marketplace %} Developer Agreement](/articles/github-marketplace-developer-agreement/)." You'll accept the terms within your [draft listing](/marketplace/listing-on-github-marketplace/creating-a-draft-github-marketplace-listing/) on {% data variables.product.product_name %}. Once you've submitted your app, one of the {% data variables.product.prodname_marketplace %} onboarding specialists will reach out to you with more information about the onboarding process, and review your app to ensure it meets these requirements:
+### Requirements for all {% data variables.product.prodname_marketplace %} listings
-### User experience
+All listings on {% data variables.product.prodname_marketplace %} should be for tools that provide value to the {% data variables.product.product_name %} community. When you submit your listing for publication, you must read and accept the terms of the "[{% data variables.product.prodname_marketplace %} Developer Agreement](/articles/github-marketplace-developer-agreement/)."
-- {% data variables.product.prodname_github_app %}s should have a minimum of 100 installations.
-- {% data variables.product.prodname_oauth_app %}s should have a minimum of 200 users.
+#### User experience requirements for all apps
+
+All listings should meet the following requirements, regardless of whether they are for a free or paid app.
+
+- Listings must not actively persuade users away from {% data variables.product.product_name %}.
+- Listings must include valid contact information for the publisher.
+- Listings must have a relevant description of the application.
+- Listings must specify a pricing plan.
- Apps must provide value to customers and integrate with the platform in some way beyond authentication.
- Apps must be publicly available in {% data variables.product.prodname_marketplace %} and cannot be in beta or available by invite only.
-- Apps cannot actively persuade users away from {% data variables.product.product_name %}.
-- Marketing materials for the app must accurately represent the app's behavior.
-- Apps must include links to user-facing documentation that describe how to set up and use the app.
-- When a customer purchases an app and GitHub redirects them to the app's installation URL, the app must begin the OAuth flow immediately. For details, see "[Handling new purchases and free trials](/marketplace/integrating-with-the-github-marketplace-api/handling-new-purchases-and-free-trials/#step-3-authorization)."
+- Apps must have webhook events set up to notify the publisher of any plan changes or cancellations using the {% data variables.product.prodname_marketplace %} API. For more information, see "[Using the {% data variables.product.prodname_marketplace %} API in your app](/developers/github-marketplace/using-the-github-marketplace-api-in-your-app)."
-- Customers must be able to install your app and select repositories on both a personal and organization account. They should be able to view and manage those accounts separately.
+For more information on providing a good customer experience, see "[Customer experience best practices for apps](/developers/github-marketplace/customer-experience-best-practices-for-apps)."
-### Brand and listing
+#### Brand and listing requirements for all apps
-- Apps that use GitHub logos must follow the "[{% data variables.product.product_name %} Logos and Usage](https://github.com/logos)" guidelines.
+- Apps that use GitHub logos must follow the {% data variables.product.company_short %} guidelines. For more information, see "[{% data variables.product.company_short %} Logos and Usage](https://github.com/logos)."
- Apps must have a logo, feature card, and screenshots images that meet the recommendations provided in "[Writing {% data variables.product.prodname_marketplace %} listing descriptions](/marketplace/listing-on-github-marketplace/writing-github-marketplace-listing-descriptions/)."
- Listings must include descriptions that are well written and free of grammatical errors. For guidance in writing your listing, see "[Writing {% data variables.product.prodname_marketplace %} listing descriptions](/marketplace/listing-on-github-marketplace/writing-github-marketplace-listing-descriptions/)."
-### Security
+To protect your customers, we recommend that you also follow security best practices. For more information, see "[Security best practices for apps](/developers/github-marketplace/security-best-practices-for-apps)."
-Apps will go through a security review before being listed on {% data variables.product.prodname_marketplace %}. A successful review will meet the requirements and follow the security best practices listed in "[Security review process](/marketplace/getting-started/security-review-process/)." For information on the review process, contact [marketplace@github.com](mailto:marketplace@github.com).
+### Considerations for free apps
-### Billing flows
+{% data reusables.marketplace.free-apps-encouraged %}
-Your app must integrate [billing flows](/marketplace/integrating-with-the-github-marketplace-api/#billing-flows) using the [{% data variables.product.prodname_marketplace %} webhook event](/marketplace/integrating-with-the-github-marketplace-api/github-marketplace-webhook-events/).
+### Requirements for paid apps
-#### Free apps
+In addition to the requirements for all apps above, each app that you offer as a paid service on {% data variables.product.prodname_marketplace %} must also meet the following requirements:
-{% data reusables.marketplace.free-apps-encouraged %} If you are listing a free app, you'll need to meet these requirements:
+- {% data variables.product.prodname_github_app %}s should have a minimum of 100 installations.
+- {% data variables.product.prodname_oauth_app %}s should have a minimum of 200 users.
+- All paid apps must handle {% data variables.product.prodname_marketplace %} purchase events for new purchases, upgrades, downgrades, cancellations, and free trials. For more information, see "[Billing requirements for paid apps](#billing-requirements-for-paid-apps)" below.
+- Publishing organizations must have a verified domain and must enable two-factor authentication. For more information, see "[Requiring two-factor authentication in your organization](/github/setting-up-and-managing-organizations-and-teams/requiring-two-factor-authentication-in-your-organization.")
-- Customers must be able to see that they have a free plan in the billing, profile, or account settings section of the app.
-- When a customer cancels your app, you must follow the flow for [cancelling plans](/marketplace/integrating-with-the-github-marketplace-api/cancelling-plans/).
+When you are ready to publish the app on {% data variables.product.prodname_marketplace %} you must request verification for the listing.
-#### Paid apps
+{% note %}
-To offer your app as a paid service, you'll need to meet these requirements to list your app on {% data variables.product.prodname_marketplace %}:
+The verification process is open to organizations. {% data reusables.marketplace.app-transfer-to-org-for-verification %} For information on how to do this, see: "[Submitting your listing for publication](/developers/github-marketplace/submitting-your-listing-for-publication#transferring-an-app-to-an-organization-before-you-submit)."
+
+{% endnote %}
+
+### Billing requirements for paid apps
+
+Your app does not need to handle payments but does need to use {% data variables.product.prodname_marketplace %} purchase events to manage new purchases, upgrades, downgrades, cancellations, and free trials. For information about how integrate these events into your app, see "[Using the {% data variables.product.prodname_marketplace %} API in your app](/developers/github-marketplace/using-the-github-marketplace-api-in-your-app)."
+
+Using GitHub's billing API allows customers to purchase an app without leaving GitHub and to pay for the service with the payment method already attached to their {% data variables.product.product_name %} account.
-- To sell your app in {% data variables.product.prodname_marketplace %}, it must use GitHub's billing system. Your app does not need to handle payments but does need to use "[{% data variables.product.prodname_marketplace %} purchase events](/marketplace/integrating-with-the-github-marketplace-api/github-marketplace-webhook-events/)" to manage new purchases, upgrades, downgrades, cancellations, and free trials. See "[Billing flows](/marketplace/integrating-with-the-github-marketplace-api/#billing-flows)" to learn about how to integrate these events into your app. Using GitHub's billing system allows customers to purchase an app without leaving GitHub and pay for the service with the payment method already attached to their {% data variables.product.product_name %} account.
- Apps must support both monthly and annual billing for paid subscriptions purchases.
- Listings may offer any combination of free and paid plans. Free plans are optional but encouraged. For more information, see "[Setting a {% data variables.product.prodname_marketplace %} listing's pricing plan](/marketplace/listing-on-github-marketplace/setting-a-github-marketplace-listing-s-pricing-plan/)."
-{% data reusables.marketplace.marketplace-billing-ui-requirements %}
diff --git a/content/developers/github-marketplace/security-best-practices-for-apps.md b/content/developers/github-marketplace/security-best-practices-for-apps.md
new file mode 100644
index 0000000000..fac3d2fcf6
--- /dev/null
+++ b/content/developers/github-marketplace/security-best-practices-for-apps.md
@@ -0,0 +1,60 @@
+---
+title: Security best practices for apps
+intro: 'Guidelines for preparing a secure app to share on {% data variables.product.prodname_marketplace %}.'
+redirect_from:
+ - /apps/marketplace/getting-started/security-review-process/
+ - /marketplace/getting-started/security-review-process
+ - /developers/github-marketplace/security-review-process-for-submitted-apps
+shortTitle: Security best practice
+versions:
+ free-pro-team: '*'
+---
+
+If you follow these best practices it will help you to provide a secure user experience.
+
+### Authorization, authentication, and access control
+
+We recommend creating a GitHub App rather than an OAuth App. {% data reusables.marketplace.github_apps_preferred %}. See "[Differences between GitHub Apps and OAuth Apps](/apps/differences-between-apps/)" for more details.
+- Apps should use the principle of least privilege and should only request the OAuth scopes and GitHub App permissions that the app needs to perform its intended functionality. For more information, see [Principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) in Wikipedia.
+- Apps should provide customers with a way to delete their account, without having to email or call a support person.
+- Apps should not share tokens between different implementations of the app. For example, a desktop app should have a separate token from a web-based app. Individual tokens allow each app to request the access needed for GitHub resources separately.
+- Design your app with different user roles, depending on the functionality needed by each type of user. For example, a standard user should not have access to admin functionality, and billing managers might not need push access to repository code.
+- Apps should not share service accounts such as email or database services to manage your SaaS service.
+- All services used in your app should have unique login and password credentials.
+- Admin privilege access to the production hosting infrastructure should only be given to engineers and employees with administrative duties.
+- Apps should not use personal access tokens to authenticate and should authenticate as an [OAuth App](/apps/about-apps/#about-oauth-apps) or a [GitHub App](/apps/about-apps/#about-github-apps):
+ - OAuth Apps should authenticate using an [OAuth token](/apps/building-oauth-apps/authorizing-oauth-apps/).
+ - GitHub Apps should authenticate using either a [JSON Web Token (JWT)](/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-a-github-app), [OAuth token](/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps/), or [installation access token](/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-an-installation).
+
+### Data protection
+
+- Apps should encrypt data transferred over the public internet using HTTPS, with a valid TLS certificate, or SSH for Git.
+- Apps should store client ID and client secret keys securely. We recommend storing them as [environmental variables](http://en.wikipedia.org/wiki/Environment_variable#Getting_and_setting_environment_variables).
+- Apps should delete all GitHub user data within 30 days of receiving a request from the user, or within 30 days of the end of the user's legal relationship with GitHub.
+- Apps should not require the user to provide their GitHub password.
+- Apps should encrypt tokens, client IDs, and client secrets.
+
+### Logging and monitoring
+
+Apps should have logging and monitoring capabilities. App logs should be retained for at least 30 days and archived for at least one year.
+A security log should include:
+
+- Authentication and authorization events
+- Service configuration changes
+- Object reads and writes
+- All user and group permission changes
+- Elevation of role to admin
+- Consistent timestamping for each event
+- Source users, IP addresses, and/or hostnames for all logged actions
+
+### Incident response workflow
+
+To provide a secure experience for users, you should have a clear incident response plan in place before listing your app. We recommend having a security and operations incident response team in your company rather than using a third-party vendor. You should have the capability to notify {% data variables.product.product_name %} within 24 hours of a confirmed incident.
+
+For an example of an incident response workflow, see the "Data Breach Response Policy" on the [SANS Institute website](https://www.sans.org/information-security-policy/). A short document with clear steps to take in the event of an incident is more valuable than a lengthy policy template.
+
+### Vulnerability management and patching workflow
+
+You should conduct regular vulnerability scans of production infrastructure. You should triage the results of vulnerability scans and define a period of time in which you agree to remediate the vulnerability.
+
+If you are not ready to set up a full vulnerability management program, it's useful to start by creating a patching process. For guidance in creating a patch management policy, see this TechRepublic article "[Establish a patch management policy](https://www.techrepublic.com/blog/it-security/establish-a-patch-management-policy-87756/)."
diff --git a/content/developers/github-marketplace/security-review-process-for-submitted-apps.md b/content/developers/github-marketplace/security-review-process-for-submitted-apps.md
deleted file mode 100644
index d1dc369fee..0000000000
--- a/content/developers/github-marketplace/security-review-process-for-submitted-apps.md
+++ /dev/null
@@ -1,94 +0,0 @@
----
-title: Security review process for submitted apps
-intro: 'GitHub''s security team reviews all apps submitted to {% data variables.product.prodname_marketplace %} to ensure that they meet security requirements. Follow these best practices to be prepared for the review process.'
-redirect_from:
- - /apps/marketplace/getting-started/security-review-process/
- - /marketplace/getting-started/security-review-process
-versions:
- free-pro-team: '*'
----
-
-
-
-After you've submitted your app for approval, the GitHub security team will request that you complete a security questionnaire about your app and overall security program. As part of the review, you will have the option to provide documentation to support your responses. You must submit two required documents before your app will be approved for {% data variables.product.prodname_marketplace %}: an [incident response plan](#incident-response-plan) and [vulnerability management workflow](#vulnerability-management-workflow).
-
-
-### Security best practices
-
-Follow these best practices to have a successful security review and provide a secure user experience.
-
-#### Authorization, authentication, and access control
-
-We recommend submitting a GitHub App rather than an OAuth App. {% data reusables.marketplace.github_apps_preferred %}. See "[Differences between GitHub Apps and OAuth Apps](/apps/differences-between-apps/)" for more details.
-- Apps must use the "[principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege)" and should only request the OAuth scopes and GitHub App permissions that the app needs to perform its intended functionality.
-- Apps must provide customers with a way to delete their account, without having to email or call a support person.
-- Apps should not share tokens between different implementations of the app. For example, a desktop app should have a separate token from a web-based app. Individual tokens allow each app to request the access needed for GitHub resources separately.
-- Design your app with different user roles, depending on the functionality needed by each type of user. For example, a standard user should not have access to admin functionality, and billing managers might not need push access to repository code.
-- Your app should not share service accounts such as email or database services to manage your SaaS service.
-- All services used in your app should have unique login and password credentials.
-- Admin privilege access to the production hosting infrastructure should only be given to engineers and employees with administrative duties.
-- Apps cannot use personal access tokens to authenticate and must authenticate as an [OAuth App](/apps/about-apps/#about-oauth-apps) or [GitHub App](/apps/about-apps/#about-github-apps):
- - OAuth Apps must authenticate using an [OAuth token](/apps/building-oauth-apps/authorizing-oauth-apps/).
- - GitHub Apps must authenticate using either a [JSON Web Token (JWT)](/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-a-github-app), [OAuth token](/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps/), or [installation access token](/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-an-installation).
-
-#### Data protection
-
-- Apps must encrypt data transferred over the public internet using HTTPS, with a valid TLS certificate, or SSH for Git.
-- Apps must store client ID and client secret keys securely. We recommend storing them as [environmental variables](http://en.wikipedia.org/wiki/Environment_variable#Getting_and_setting_environment_variables).
-- Apps must delete all GitHub user data within 30 days of receiving a request from the user, or within 30 days of the end of the user's legal relationship with GitHub.
-- Apps cannot require the user to provide their GitHub password.
-- Apps should encrypt tokens, client IDs, and client secrets.
-
-#### Logging and monitoring
-
-- Apps must have logging and monitoring capabilities. App logs must be retained for at least 30 days and archived for at least one year.
-A security log should include:
- - Authentication and authorization events
- - Service configuration changes
- - Object reads and writes
- - All user and group permission changes
- - Elevation of role to admin
- - Consistent timestamping for each event
- - Source users, IP addresses, and/or hostnames for all logged actions
-
-#### Incident response workflow
-
-- To partner with GitHub, you are required to have an [incident response plan](#incident-response-plan) in place before submitting your {% data variables.product.prodname_marketplace %} app listing.
-- We recommend having a security and operations incident response team in your company rather than using a third-party vendor.
-- You should have the capability to notify GitHub within 24 hours of a confirmed incident.
-- You should familiarize yourself with sections 3.7.5 - 3.7.5.6 of the [{% data variables.product.prodname_marketplace %} Developer Agreement](/github/site-policy/github-marketplace-developer-agreement#3-restrictions-and-responsibilities), which include additional details on incident response workflow requirements.
-
-#### Vulnerability management and patching workflow
-
-- You should conduct regular vulnerability scans of production infrastructure.
-- You should triage the results of vulnerability scans and define a period of time in which you agree to remediate the vulnerability.
-- You should familiarize yourself with section 3.7.3 of the [{% data variables.product.prodname_marketplace %} Developer Agreement](/github/site-policy/github-marketplace-developer-agreement#3-restrictions-and-responsibilities), which includes additional details on vulnerability management and patching workflows requirements.
-
-### Security program documentation
-
-During the Marketplace security review, you will be asked to submit your incident response plan and vulnerability management workflow. Each document must include a company-branded statement signed by management with a date stamp.
-
-#### Incident response plan
-Your incident response plan documentation must include the current process that your company follows, who is accountable, and the person to contact or expect contact from if an incident occurs. The "[NIST Computer Security Incident Handling Guide](http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-61r2.pdf)" is a great example of a document that covers incident response in general. Section 2.3 "Incident Response Policy, Plan, and Procedure Creation" specifically covers the policy. Another great example is the "[SANS Data Breach Response Policy](https://www.sans.org/security-resources/policies/general/pdf/data-breach-response)."
-
-#### Vulnerability management workflow
-Your vulnerability management workflow documentation must include the current process that your company follows for vulnerability management and the patching process used. If you don't have a full vulnerability management program, it might help to start by creating a patching process. For guidance in creating a patch management policy, read the article "[Establish a patch management policy](https://www.techrepublic.com/blog/it-security/establish-a-patch-management-policy-87756/)."
-
-{% note %}
-
-**Note:** The incident response and vulnerability management workflow documents aren't expected to be massive formal policy or program documents. A page or two about what you do is more valuable than a lengthy policy template.
-
-{% endnote %}
-
-#### GitHub Marketplace security program questionnaire
-
-During the app submission process, our {% data variables.product.prodname_marketplace %} onboarding team will also send you a questionnaire requesting information about your security practices. This document will serve as a written record attesting:
-
-- The authentication method and scopes required by your app.
-- That you're not requesting more scopes or {% data variables.product.product_name %} access than is needed for the app to perform its intended functionality, taking OAuth limitations and use of {% data variables.product.prodname_github_app %}s into account.
-- The use of any third-party services or infrastructure, such as SaaS, PaaS, or IaaS.
-- An incident response procedure exists.
-- Your app's method of key/token handling.
-- That a responsible disclosure policy and process in place or plans to implement one within six months.
-- Your vulnerability management workflow or program.
-- That you have logging and monitoring capabilities. You must also provide evidence that any relevant app logs are retained for at least 30 days and archived for at least one year.
diff --git a/content/developers/github-marketplace/setting-pricing-plans-for-your-listing.md b/content/developers/github-marketplace/setting-pricing-plans-for-your-listing.md
index 205978a5f6..9ab919817d 100644
--- a/content/developers/github-marketplace/setting-pricing-plans-for-your-listing.md
+++ b/content/developers/github-marketplace/setting-pricing-plans-for-your-listing.md
@@ -1,6 +1,6 @@
---
title: Setting pricing plans for your listing
-intro: 'When [listing your app on {% data variables.product.prodname_marketplace %}](/marketplace/listing-on-github-marketplace/), you can choose to provide your app as a free service or sell your app. If you plan to sell your app, you can create different pricing plans for different feature tiers.'
+intro: 'When you list your app on {% data variables.product.prodname_marketplace %}, you can choose to provide your app as a free service or sell your app. If you plan to sell your app, you can create different pricing plans for different feature tiers.'
redirect_from:
- /apps/adding-integrations/managing-pricing-and-payments-for-a-github-marketplace-listing/setting-a-github-marketplace-listing-s-pricing-plan/
- /apps/marketplace/managing-pricing-and-payments-for-a-github-marketplace-listing/setting-a-github-marketplace-listing-s-pricing-plan/
@@ -17,57 +17,52 @@ versions:
free-pro-team: '*'
---
+### About setting pricing plans
+If you want to sell an app on {% data variables.product.prodname_marketplace %}, you need to request verification when you publish the listing for your app. During the verification process, an onboarding expert checks the organization's identity and security settings. The onboarding expert will also take the organization through financial onboarding. For more information, see: "[Requirements for listing an app on {% data variables.product.prodname_marketplace %}](/marketplace/getting-started/requirements-for-listing-an-app-on-github-marketplace/)."
+
+{% data reusables.marketplace.app-transfer-to-org-for-verification %} For information on how to do this, see: "[Submitting your listing for publication](/developers/github-marketplace/submitting-your-listing-for-publication#transferring-an-app-to-an-organization-before-you-submit)."
+
+{% data variables.product.prodname_marketplace %} offers several different types of pricing plan. For detailed information, see "[Pricing plans for {% data variables.product.prodname_marketplace %}](/developers/github-marketplace/pricing-plans-for-github-marketplace-apps)."
+
+### About saving pricing plans
+
+You can save pricing plans in a draft or published state. If you haven't submitted your {% data variables.product.prodname_marketplace %} listing for approval, a published plan will function in the same way as a draft plan until your listing is approved and shown on {% data variables.product.prodname_marketplace %}. Draft plans allow you to create and save new pricing plans without making them available on your {% data variables.product.prodname_marketplace %} listing page. Once you publish a pricing plan on a published listing, it's available for customers to purchase immediately. You can publish up to 10 pricing plans.
+
+For guidelines on billing customers, see "[Billing customers](/developers/github-marketplace/billing-customers)."
### Creating pricing plans
-To learn about the types of pricing plans that {% data variables.product.prodname_marketplace %} offers, see "[{% data variables.product.prodname_marketplace %} Pricing Plans](/marketplace/selling-your-app/github-marketplace-pricing-plans/)." You'll also find helpful billing guidelines in "[Selling your app](/marketplace/selling-your-app/)."
-
-Pricing plans can be in the draft or published state. If you haven't submitted your {% data variables.product.prodname_marketplace %} listing for approval, a published listing will function the same way as draft listings until your app is approved and listed on {% data variables.product.prodname_marketplace %}. Draft listings allow you to create and save new pricing plans without making them available on your {% data variables.product.prodname_marketplace %} listing page. Once you publish the pricing plan, it's available for customers to purchase immediately. You can publish up to 10 pricing plans.
-
-To create a pricing plan for your {% data variables.product.prodname_marketplace %} listing, click **Plans and pricing** in the left sidebar of your [{% data variables.product.prodname_marketplace %} listing page](https://github.com/marketplace/manage). If you haven't created a {% data variables.product.prodname_marketplace %} listing yet, read "[Creating a draft {% data variables.product.prodname_marketplace %} listing](/marketplace/listing-on-github-marketplace/creating-a-draft-github-marketplace-listing/)" to learn how.
+To create a pricing plan for your {% data variables.product.prodname_marketplace %} listing, click **Plans and pricing** in the left sidebar of your [{% data variables.product.prodname_marketplace %} listing page](https://github.com/marketplace/manage). For more information, see "[Creating a draft {% data variables.product.prodname_marketplace %} listing](/marketplace/listing-on-github-marketplace/creating-a-draft-github-marketplace-listing/)."
When you click **New draft plan**, you'll see a form that allows you to customize your pricing plan. You'll need to configure the following fields to create a pricing plan:
-#### Plan name
+- **Plan name** - Your pricing plan's name will appear on your {% data variables.product.prodname_marketplace %} app's landing page. You can customize the name of your pricing plan to align with the plan's resources, the size of the company that will use the plan, or anything you'd like.
-Your pricing plan's name will appear on your {% data variables.product.prodname_marketplace %} app's landing page. You can customize the name of your pricing plan to align to the plan's resources, the size of the company that will use the plan, or anything you'd like.
+- **Pricing models** - There are three types of pricing plan: free, flat-rate, and per-unit. All plans require you to process new purchase and cancellation events from the marketplace API. In addition, for paid plans:
-#### Pricing models
+ - You must set a price for both monthly and yearly subscriptions in US dollars.
+ - Your app must process plan change events.
+ - You must request verification to publish a listing with a paid plan.
+ - {% data reusables.marketplace.marketplace-pricing-free-trials %}
-##### Free plans
+ For detailed information, see "[Pricing plans for {% data variables.product.prodname_marketplace %} apps](/developers/github-marketplace/pricing-plans-for-github-marketplace-apps)" and "[Using the {% data variables.product.prodname_marketplace %} API in your app](/developers/github-marketplace/using-the-github-marketplace-api-in-your-app)."
-{% data reusables.marketplace.free-apps-encouraged %} A free plan still requires you to handle [new purchase](/marketplace/integrating-with-the-github-marketplace-api/handling-new-purchases-and-free-trials/) and [cancellation](/marketplace/integrating-with-the-github-marketplace-api/cancelling-plans/) billing flows. See "[Billing flows](/marketplace/integrating-with-the-github-marketplace-api/#billing-flows)" for more details.
+- **Available for** - {% data variables.product.prodname_marketplace %} pricing plans can apply to **Personal and organization accounts**, **Personal accounts only**, or **Organization accounts only**. For example, if your pricing plan is per-unit and provides multiple seats, you would select **Organization accounts only** because there is no way to assign seats to people in an organization from a personal account.
-##### Flat-rate plans
+- **Short description** - Write a brief summary of the details of the pricing plan. The description might include the type of customer the plan is intended for or the resources the plan includes.
-Flat-rate pricing plans allow you to offer your service to customers for a flat-rate fee. {% data reusables.marketplace.marketplace-pricing-free-trials %}
+- **Bullets** - You can write up to four bullets that include more details about your pricing plan. The bullets might include the use cases of your app or list more detailed information about the resources or features included in the plan.
-You must set a price for both monthly and yearly subscriptions in U.S. Dollars for flat-rate plans.
-
-##### Per-unit plans
-
-Per-unit pricing allows you to offer your app in units. For example, a unit can be a person, seat, or user. You'll need to provide a name for the unit and set a price for both monthly and yearly subscriptions, in U.S. Dollars.
-
-#### Available for
-
-{% data variables.product.prodname_marketplace %} pricing plans can apply to **Personal and organization accounts**, **Personal accounts only**, or **Organization accounts only**. For example, if your pricing plan is per-unit and provides multiple seats, you would select **Organization accounts only** because there is no way to assign seats to people in an organization from a personal account.
-
-#### Short description
-
-Write a brief summary of the details of the pricing plan. The description might include the type of customer the plan is intended for or the resources the plan includes.
-
-#### Bullets
-
-You can write up to four bullets that include more details about your pricing plan. The bullets might include the use cases of your app or list more detailed information about the resources or features included in the plan.
+{% data reusables.marketplace.free-plan-note %}
### Changing a {% data variables.product.prodname_marketplace %} listing's pricing plan
-If a pricing plan for your {% data variables.product.prodname_marketplace %} plan is no longer needed or if you need to adjust pricing details, you can remove it.
+If a pricing plan for your {% data variables.product.prodname_marketplace %} listing is no longer needed, or if you need to adjust pricing details, you can remove it.

-Once you publish a pricing plan for an app already listed in the {% data variables.product.prodname_marketplace %}, you can't make changes to the plan. Instead, you'll need to remove the pricing plan. Customers who already purchased the removed pricing plan will continue to use it until they opt out and move onto a new pricing plan. For more on pricing plans, see "[{% data variables.product.prodname_marketplace %} pricing plans](/marketplace/selling-your-app/github-marketplace-pricing-plans/)."
+Once you publish a pricing plan for an app that is already listed in {% data variables.product.prodname_marketplace %}, you can't make changes to the plan. Instead, you'll need to remove the pricing plan and create a new plan. Customers who already purchased the removed pricing plan will continue to use it until they opt out and move onto a new pricing plan. For more on pricing plans, see "[{% data variables.product.prodname_marketplace %} pricing plans](/marketplace/selling-your-app/github-marketplace-pricing-plans/)."
Once you remove a pricing plan, users won't be able to purchase your app using that plan. Existing users on the removed pricing plan will continue to stay on the plan until they cancel their plan subscription.
diff --git a/content/developers/github-marketplace/submitting-your-listing-for-publication.md b/content/developers/github-marketplace/submitting-your-listing-for-publication.md
new file mode 100644
index 0000000000..b25c4580ae
--- /dev/null
+++ b/content/developers/github-marketplace/submitting-your-listing-for-publication.md
@@ -0,0 +1,37 @@
+---
+title: Submitting your listing for publication
+intro: 'You can submit your listing for the {% data variables.product.prodname_dotcom %} community to use.'
+redirect_from:
+ - /marketplace/listing-on-github-marketplace/submitting-your-listing-for-review
+ - /developers/github-marketplace/submitting-your-listing-for-review
+versions:
+ free-pro-team: '*'
+---
+
+
+
+Once you've completed the listing for your app, you'll see two buttons that allow you to request publication of the listing with or without verification. The **Request** button for "Publish without verification" is disabled if you have published any paid pricing plans in the listing.
+
+
+
+{% data reusables.marketplace.launch-with-free %}
+
+After you submit your listing for review, an onboarding expert will reach out to you with additional information.
+
+For an overview of the process for creating and submitting a listing, see "[About {% data variables.product.prodname_marketplace %}](/developers/github-marketplace/about-github-marketplace#publishing-an-app-to-github-marketplace)."
+
+### Prerequisites for publishing with verification
+
+Before you request verification of your listing, you'll need to integrate the {% data variables.product.prodname_marketplace %} billing flows and webhook into your app. For more information, see "[Using the {% data variables.product.prodname_marketplace %} API in your app](/developers/github-marketplace/using-the-github-marketplace-api-in-your-app)."
+
+If you've met the requirements for listing and you've integrated with the {% data variables.product.prodname_marketplace %} API, go ahead and submit your listing. For more information, see "[Requirements for listing an app](/developers/github-marketplace/requirements-for-listing-an-app)."
+
+{% data reusables.marketplace.app-transfer-to-org-for-verification %} For information on how to do this, see: "[Transferring an app to an organization before you submit](#transferring-an-app-to-an-organization-before-you-submit)" below.
+
+### Transferring an app to an organization before you submit
+
+You cannot sell an app that's owned by a user account. You need to transfer the app to an organization that is already a verified creator, or that can request verification for a listing for the app. For details, see:
+
+1. "[Creating an organization from scratch](/github/setting-up-and-managing-organizations-and-teams/creating-a-new-organization-from-scratch)"
+
+1. "[Transferring ownership of a GitHub App](/developers/apps/transferring-ownership-of-a-github-app)" or "[Transferring ownership of an OAuth App](/developers/apps/transferring-ownership-of-an-oauth-app)"
diff --git a/content/developers/github-marketplace/submitting-your-listing-for-review.md b/content/developers/github-marketplace/submitting-your-listing-for-review.md
deleted file mode 100644
index 98a838e1f9..0000000000
--- a/content/developers/github-marketplace/submitting-your-listing-for-review.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: Submitting your listing for review
-intro: 'You can submit your listing as a verified or unverified app for the {% data variables.product.prodname_dotcom %} community to use.'
-redirect_from:
- - /marketplace/listing-on-github-marketplace/submitting-your-listing-for-review
-versions:
- free-pro-team: '*'
----
-
-
-
-Once you've completed your app listing, you'll see two buttons that allow you to submit an unverified and verified app. The Publish without Verification **Request** button will not be available if you have published any paid pricing plans.
-
-
-
-{% data reusables.marketplace.launch-with-free %}
-
-Before you can submit a verified app, you'll need to integrate the {% data variables.product.prodname_marketplace %} billing flows and webhook into your existing app. See [Verified apps](/marketplace/#verified-apps) for the steps required to submit your app.
-
-If you've met the [requirements](/marketplace/getting-started/requirements-for-listing-an-app-on-github-marketplace/) for a verified {% data variables.product.prodname_marketplace %} listing and you've integrated with the {% data variables.product.prodname_marketplace %} API, go ahead and submit your listing!
-
-After you submit your listing for review, the {% data variables.product.prodname_marketplace %} onboarding team will reach out to you with additional information.
diff --git a/content/developers/github-marketplace/testing-your-app.md b/content/developers/github-marketplace/testing-your-app.md
index 09e79b1a7a..577b6d624a 100644
--- a/content/developers/github-marketplace/testing-your-app.md
+++ b/content/developers/github-marketplace/testing-your-app.md
@@ -1,6 +1,6 @@
---
title: Testing your app
-intro: 'GitHub recommends testing your app with APIs and webhooks before submitting your listing to {% data variables.product.prodname_marketplace %} so you can provide an ideal experience for customers. Before the {% data variables.product.prodname_marketplace %} onboarding team approves your app, it must adequately handle the [billing flows](/marketplace/integrating-with-the-github-marketplace-api/#billing-flows).'
+intro: 'GitHub recommends testing your app with APIs and webhooks before submitting your listing to {% data variables.product.prodname_marketplace %} so you can provide an ideal experience for customers. Before an onboarding expert approves your app, it must adequately handle the billing flows.'
redirect_from:
- /apps/marketplace/testing-apps-apis-and-webhooks/
- /apps/marketplace/integrating-with-the-github-marketplace-api/testing-github-marketplace-apps/
@@ -13,7 +13,7 @@ versions:
### Testing apps
-You can use a [draft {% data variables.product.prodname_marketplace %} listing](/marketplace/listing-on-github-marketplace/creating-a-draft-github-marketplace-listing/) to simulate each of the [billing flows](/marketplace/integrating-with-the-github-marketplace-api/#billing-flows). A listing in the draft state means that it has not been submitted for approval. Any purchases you make using a draft {% data variables.product.prodname_marketplace %} listing will _not_ create real transactions, and GitHub will not charge your credit card.
+You can use a draft {% data variables.product.prodname_marketplace %} listing to simulate each of the billing flows. A listing in the draft state means that it has not been submitted for approval. Any purchases you make using a draft {% data variables.product.prodname_marketplace %} listing will _not_ create real transactions, and GitHub will not charge your credit card. For more information, see "[Drafting a listing for your app](/developers/github-marketplace/drafting-a-listing-for-your-app)" and "[Using the {% data variables.product.prodname_marketplace %} API in your app](/developers/github-marketplace/using-the-github-marketplace-api-in-your-app)."
#### Using a development app with a draft listing to test changes
diff --git a/content/developers/github-marketplace/webhook-events-for-the-github-marketplace-api.md b/content/developers/github-marketplace/webhook-events-for-the-github-marketplace-api.md
index e29643da16..96594bbc61 100644
--- a/content/developers/github-marketplace/webhook-events-for-the-github-marketplace-api.md
+++ b/content/developers/github-marketplace/webhook-events-for-the-github-marketplace-api.md
@@ -1,6 +1,6 @@
---
title: Webhook events for the GitHub Marketplace API
-intro: 'A {% data variables.product.prodname_marketplace %} app receives information about changes to a user''s plan from the Marketplace purchase event webhook. A Marketplace purchase event is triggered when a user purchases, cancels, or changes their payment plan. For details on how to respond to each of these types of events, see "[Billing flows](/marketplace/integrating-with-the-github-marketplace-api/#billing-flows)."'
+intro: 'A {% data variables.product.prodname_marketplace %} app receives information about changes to a user''s plan from the Marketplace purchase event webhook. A Marketplace purchase event is triggered when a user purchases, cancels, or changes their payment plan.'
redirect_from:
- /apps/marketplace/setting-up-github-marketplace-webhooks/about-webhook-payloads-for-a-github-marketplace-listing/
- /apps/marketplace/integrating-with-the-github-marketplace-api/github-marketplace-webhook-events/
diff --git a/content/discussions/index.md b/content/discussions/index.md
index 9dff7d00d3..66dca4f270 100644
--- a/content/discussions/index.md
+++ b/content/discussions/index.md
@@ -22,6 +22,7 @@ featuredLinks:
- /discussions/guides/finding-discussions-across-multiple-repositories
- /discussions/collaborating-with-your-community-using-discussions/collaborating-with-maintainers-using-discussions
- /discussions/managing-discussions-for-your-community/managing-categories-for-discussions-in-your-repository
+product_video: https://www.youtube-nocookie.com/embed/DbTWBP3_RbM
layout: product-landing
versions:
free-pro-team: '*'
diff --git a/content/discussions/quickstart.md b/content/discussions/quickstart.md
index 7625961cdb..252da3b6cc 100644
--- a/content/discussions/quickstart.md
+++ b/content/discussions/quickstart.md
@@ -10,7 +10,7 @@ versions:
### Introduction
-{% data variables.product.prodname_discussions %} {% data variables.product.prodname_discussions %} is a collaborative communication forum for the community around an open source project. Discussions are for conversations that need to be transparent and accessible but do not need to be tracked on a project board and are not related to code, unlike issues. Discussions enable fluid, open conversation in a public forum.
+{% data variables.product.prodname_discussions %} is a collaborative communication forum for the community around an open source project. Discussions are for conversations that need to be transparent and accessible but do not need to be tracked on a project board and are not related to code, unlike issues. Discussions enable fluid, open conversation in a public forum.
Discussions give a space for more collaborative conversations by connecting and giving a more centralized area to connect and find information.
@@ -21,7 +21,9 @@ Repository owners and people with write access can enable {% data variables.prod
When you first enable a {% data variables.product.prodname_discussions %}, you will be invited to configure a welcome post.
{% data reusables.repositories.navigate-to-repo %}
-{% data reusables.repositories.sidebar-settings %}
+1. Under your repository name, click {% octicon "gear" aria-label="The gear icon" %}
+**Settings**.
+
1. Under "Features", click **Set up discussions**.

1. Under "Start a new discussion," edit the template to align with the resources and tone you want to set for your community.
@@ -57,4 +59,4 @@ People with triage permissions for a repository can help moderate a project's di
### Next steps
-Once there is a clear path to scope work out and move an idea from concept to reality, you can create an issue and start tracking your progress. For more information on creating an issue from a discussion, see, see "[Moderating discussions](/discussions/managing-discussions-for-your-community/moderating-discussions)."
+Once there is a clear path to scope work out and move an idea from concept to reality, you can create an issue and start tracking your progress. For more information on creating an issue from a discussion, see "[Moderating discussions](/discussions/managing-discussions-for-your-community/moderating-discussions)."
diff --git a/content/education/guides.md b/content/education/guides.md
new file mode 100644
index 0000000000..0265b53633
--- /dev/null
+++ b/content/education/guides.md
@@ -0,0 +1,45 @@
+---
+title: Guides for GitHub Education
+intro: 'These guides for {% data variables.product.prodname_education %} help you teach and learn both {% data variables.product.product_name %} and software development.'
+allowTitleToDifferFromFilename: true
+versions:
+ free-pro-team: '*'
+---
+
+### Get started with {% data variables.product.product_name %}
+
+Teachers, students, and researchers can use tools from {% data variables.product.product_name %} to enrich a software development curriculum and develop real-world collaboration skills.
+
+- [Sign up for a new {% data variables.product.prodname_dotcom %} account](/github/getting-started-with-github/signing-up-for-a-new-github-account)
+- [Git and {% data variables.product.prodname_dotcom %} quickstart ](/github/getting-started-with-github/quickstart)
+- [Apply for an educator or researcher discount](/education/teach-and-learn-with-github-education/apply-for-an-educator-or-researcher-discount)
+- [Apply for a student developer pack](/education/teach-and-learn-with-github-education/apply-for-a-student-developer-pack)
+
+### Run a software development course with {% data variables.product.company_short %}
+
+Administer a classroom, assign and review work from your students, and teach the new generation of software developers with {% data variables.product.prodname_classroom %}.
+
+- [Basics of setting up {% data variables.product.prodname_classroom %} ](/education/manage-coursework-with-github-classroom/basics-of-setting-up-github-classroom)
+- [Manage classrooms](/education/manage-coursework-with-github-classroom/manage-classrooms)
+- [Create an individual assignment](/education/manage-coursework-with-github-classroom/create-an-individual-assignment)
+- [Create a group assignment](/education/manage-coursework-with-github-classroom/create-a-group-assignment)
+- [Create an assignment from a template repository](/education/manage-coursework-with-github-classroom/create-an-assignment-from-a-template-repository)
+- [Leave feedback with pull requests](/education/manage-coursework-with-github-classroom/leave-feedback-with-pull-requests)
+- [Use autograding](/education/manage-coursework-with-github-classroom/use-autograding)
+
+### Learn to develop software
+
+Incorporate {% data variables.product.prodname_dotcom %} into your education, and use the same tools as the professionals.
+
+- [Git and {% data variables.product.prodname_dotcom %} learning resources](/github/getting-started-with-github/git-and-github-learning-resources)
+- [Use {% data variables.product.prodname_dotcom %} for your schoolwork](/education/teach-and-learn-with-github-education/use-github-for-your-schoolwork)
+- [Try {% data variables.product.prodname_desktop %}](/desktop)
+- [Try {% data variables.product.prodname_cli %}](/github/getting-started-with-github/github-cli)
+
+### Contribute to the community
+
+Participate in the community, get training from {% data variables.product.company_short %}, and learn or teach new skills.
+
+- [{% data variables.product.prodname_education_community %}](https://education.github.community)
+- [About Campus Experts](/education/teach-and-learn-with-github-education/about-campus-experts)
+- [About Campus Advisors](/education/teach-and-learn-with-github-education/about-campus-advisors)
diff --git a/content/education/index.md b/content/education/index.md
new file mode 100644
index 0000000000..fee80a351c
--- /dev/null
+++ b/content/education/index.md
@@ -0,0 +1,43 @@
+---
+title: GitHub Education Documentation
+shortTitle: Education
+intro: "{% data variables.product.prodname_education %} helps you teach or learn software development with the tools and support of {% data variables.product.company_short %}'s platform and community."
+introLinks:
+ quickstart: /education/quickstart
+featuredLinks:
+ guides:
+ - /education/teach-and-learn-with-github-education/apply-for-a-student-developer-pack
+ - /education/teach-and-learn-with-github-education/apply-for-an-educator-or-researcher-discount
+ - /education/teach-and-learn-with-github-education/use-github-at-your-educational-institution
+ guideCards:
+ - /github/getting-started-with-github/signing-up-for-a-new-github-account
+ - /github/getting-started-with-github/git-and-github-learning-resources
+ - /education/manage-coursework-with-github-classroom/basics-of-setting-up-github-classroom
+ popular:
+ - /education/teach-and-learn-with-github-education/use-github-for-your-schoolwork
+ - /education/teach-and-learn-with-github-education/use-github-in-your-classroom-and-research
+ - /desktop
+ - /github/getting-started-with-github/github-cli
+ - /education/manage-coursework-with-github-classroom/teach-with-github-classroom
+
+changelog:
+ - title: 'Try something new at Local Hack Day: Learn'
+ date: '2020-10-15'
+ href: https://github.blog/2020-10-15-try-something-new-at-local-hack-day-learn/
+ - title: 'Remote Education: Creating community through shared experiences'
+ date: '2020-09-24'
+ href: https://github.blog/2020-09-24-remote-education-creating-community-through-shared-experiences/
+ - title: 'Remote Education: A series of best practices for online campus communities'
+ date: '2020-09-10'
+ href: https://github.blog/2020-09-10-remote-education-a-series-of-best-practices-for-online-campus-communities/
+ - title: Welcome to the inaugural class of MLH Fellows
+ date: '2020-06-24'
+ href: https://github.blog/2020-06-24-welcome-to-the-inaugural-class-of-mlh-fellows/
+
+layout: product-landing
+versions:
+ free-pro-team: '*'
+---
+
+
+
diff --git a/content/education/manage-coursework-with-github-classroom/about-using-makecode-arcade-with-github-classroom.md b/content/education/manage-coursework-with-github-classroom/about-using-makecode-arcade-with-github-classroom.md
new file mode 100644
index 0000000000..a608267c1c
--- /dev/null
+++ b/content/education/manage-coursework-with-github-classroom/about-using-makecode-arcade-with-github-classroom.md
@@ -0,0 +1,31 @@
+---
+title: About using MakeCode Arcade with GitHub Classroom
+shortTitle: About using MakeCode Arcade
+intro: You can configure MakeCode Arcade as the online IDE for assignments in {% data variables.product.prodname_classroom %}.
+versions:
+ free-pro-team: '*'
+redirect_from:
+ - /education/manage-coursework-with-github-classroom/student-experience-makecode
+---
+
+### About MakeCode Arcade
+
+MakeCode Arcade is an online integrated development environment (IDE) for developing retro arcade games using drag-and-drop block programming and JavaScript. Students can write, edit, run, test, and debug code in a browser with MakeCode Arcade. For more information about online IDEs and {% data variables.product.prodname_classroom %}, see "[Integrate {% data variables.product.prodname_classroom %} with an online IDE](/education/manage-coursework-with-github-classroom/integrate-github-classroom-with-an-online-ide)."
+
+{% data reusables.classroom.readme-contains-button-for-online-ide %}
+
+The first time the student clicks the button to visit MakeCode Arcade, the student must sign into MakeCode Arcade with {% data variables.product.product_name %} credentials. After signing in, the student will have access to a development environment containing the code from the assignment repository, fully configured on MakeCode Arcade.
+
+For more information about working on MakeCode Arcade, see the [MakeCode Arcade Tour](https://arcade.makecode.com/ide-tour) and [documentation](https://arcade.makecode.com/docs) on the MakeCode Arcade website.
+
+MakeCode Arcade does not support multiplayer-editing for group assignments. Instead, students can collaborate with Git and {% data variables.product.product_name %} features like branches and pull requests.
+
+### About submission of assignments with MakeCode Arcade
+
+By default, MakeCode Arcade is configured to push to the assignment repository on {% data variables.product.product_location %}. After making progress on an assignment with MakeCode Arcade, students should push changes to {% data variables.product.product_location %} using the {% octicon "mark-github" aria-label="The GitHub mark" %}{% octicon "arrow-up" aria-label="The up arrow icon" %} button at the bottom of the screen.
+
+
+
+### Further reading
+
+- "[About READMEs](/github/creating-cloning-and-archiving-repositories/about-readmes)"
diff --git a/content/education/manage-coursework-with-github-classroom/about-using-replit-with-github-classroom.md b/content/education/manage-coursework-with-github-classroom/about-using-replit-with-github-classroom.md
new file mode 100644
index 0000000000..5c4dcdb949
--- /dev/null
+++ b/content/education/manage-coursework-with-github-classroom/about-using-replit-with-github-classroom.md
@@ -0,0 +1,33 @@
+---
+title: About using Repl.it with GitHub Classroom
+shortTitle: About using Repl.it
+intro: You can configure Repl.it as the online integrated development environment (IDE) for assignments in {% data variables.product.prodname_classroom %}.
+versions:
+ free-pro-team: '*'
+redirect_from:
+ - /education/manage-coursework-with-github-classroom/student-experience-replit
+---
+
+### About Repl.it
+
+Repl.it is an online integrated development environment (IDE) that supports multiple programming languages. Students can write, edit, run, test, and debug code in a browser with Repl.it. For more information about online IDEs and {% data variables.product.prodname_classroom %}, see "[Integrate {% data variables.product.prodname_classroom %} with an online IDE](/education/manage-coursework-with-github-classroom/integrate-github-classroom-with-an-online-ide)."
+
+{% data reusables.classroom.readme-contains-button-for-online-ide %}
+
+The first time the student clicks the button to visit Repl.it, the student must sign into Repl.it with {% data variables.product.product_name %} credentials. After signing in, the student will have access to a development environment containing the code from the assignment repository, fully configured on Repl.it.
+
+For more information about working on Repl.it, see the [Repl.it Quickstart Guide](https://docs.repl.it/misc/quick-start#the-repl-environment).
+
+For group assignments, students can use Repl.it Multiplayer to work collaboratively. For more information, see the [Repl.it Multiplayer](https://repl.it/site/multiplayer) website.
+
+### About submission of assignments with Repl.it
+
+By default, Repl.it is configured to push to the assignment repository on {% data variables.product.product_location %}. After making progress on an assignment with Repl.it, students should push changes to {% data variables.product.product_location %} using the version control functionality in the left sidebar.
+
+
+
+For more information about using Git on Repl.it, see the [Repl.it + Git Tutorial](https://repl.it/talk/learn/Replit-Git-Tutorial/23331) on the Repl.it website.
+
+### Further reading
+
+- "[About READMEs](/github/creating-cloning-and-archiving-repositories/about-readmes)"
diff --git a/content/education/manage-coursework-with-github-classroom/basics-of-setting-up-github-classroom.md b/content/education/manage-coursework-with-github-classroom/basics-of-setting-up-github-classroom.md
new file mode 100644
index 0000000000..872bc8fac8
--- /dev/null
+++ b/content/education/manage-coursework-with-github-classroom/basics-of-setting-up-github-classroom.md
@@ -0,0 +1,33 @@
+---
+title: Basics of setting up GitHub Classroom
+shortTitle: '{% data variables.product.prodname_classroom %} basics'
+intro: Learn how to set up your classroom, manage assignments, and configure time-saving automation.
+versions:
+ free-pro-team: '*'
+---
+
+### Videos about {% data variables.product.prodname_classroom %}
+
+You can watch a series of short video tutorials about the configuration and use of {% data variables.product.prodname_classroom %}. To watch all videos as part of a continuous playlist, see the [{% data variables.product.prodname_classroom %} Getting Started Guide](https://www.youtube.com/playlist?list=PLIRjfNq867bewk3ZGV6Z7a16YDNRCpK3u) on YouTube.
+
+For more information about terminology for {% data variables.product.prodname_classroom %}, see "[Glossary](/education/manage-coursework-with-github-classroom/glossary)".
+
+1. Getting started {% octicon "link-external" aria-label="The external link icon" %}
+2. Adding your student roster {% octicon "link-external" aria-label="The external link icon" %}
+3. Creating assignments
+ - Creating an assignment using a {% data variables.product.prodname_dotcom %} repository {% octicon "link-external" aria-label="The external link icon" %}
+ - Creating an assignment using Microsoft MakeCode as your online IDE {% octicon "link-external" aria-label="The external link icon" %}
+ - Creating an assignment using Repl.it as your online IDE {% octicon "link-external" aria-label="The external link icon" %}
+4. How students complete assignments {% octicon "link-external" aria-label="The external link icon" %}
+5. How teachers review assignments {% octicon "link-external" aria-label="The external link icon" %}
+6. Creating group assignments {% octicon "link-external" aria-label="The external link icon" %}
+7. Next steps to get started {% octicon "link-external" aria-label="The external link icon" %}
+8. {% data variables.product.prodname_dotcom %} Teacher Toolbox {% octicon "link-external" aria-label="The external link icon" %}
+
+### Next steps
+
+For more information about teaching with {% data variables.product.prodname_classroom %}, see "[Teach with {% data variables.product.prodname_classroom %}](/education/manage-coursework-with-github-classroom/teach-with-github-classroom)."
+
+### Further reading
+
+- "[Teach and learn with {% data variables.product.prodname_education %}](/education/teach-and-learn-with-github-education)"
diff --git a/content/education/manage-coursework-with-github-classroom/configure-default-settings-for-assignment-repositories.md b/content/education/manage-coursework-with-github-classroom/configure-default-settings-for-assignment-repositories.md
new file mode 100644
index 0000000000..a0ff56492e
--- /dev/null
+++ b/content/education/manage-coursework-with-github-classroom/configure-default-settings-for-assignment-repositories.md
@@ -0,0 +1,51 @@
+---
+title: Configure default settings for assignment repositories
+shortTitle: Configure defaults for assignment repositories
+intro: You can use the Probot Settings app to configure the default settings for repositories that {% data variables.product.prodname_classroom %} creates for an assignment.
+permissions: Organization owners can configure default settings for assignment repositories by installing a {% data variables.product.prodname_github_app %} for the organization.
+versions:
+ free-pro-team: '*'
+redirect_from:
+ - /education/manage-coursework-with-github-classroom/probot-settings
+---
+
+### About configuration of defaults for assignment repositories
+
+{% data variables.product.prodname_classroom %} creates a repository that belongs for each student or team that accepts an assignment. The repository belongs to the organization that you use for {% data variables.product.prodname_classroom %}. Assignment repositories can be empty, or you can use a template repository. For more information, see "[Create an assignment from a template repository](/education/manage-coursework-with-github-classroom/create-an-assignment-from-a-template-repository)."
+
+{% data reusables.classroom.you-may-want-to-predefine-repository-settings %}
+
+With the Probot Settings app, you can create a file named _.github/settings.yml_ in a repository that contains a list of settings for the repository, and then install a {% data variables.product.prodname_github_app %} for your organization that automatically applies the settings to the repository.
+
+You can include _.github/settings.yml_ in a template repository that you use for an assignment in {% data variables.product.prodname_classroom %}. When an individual or team accepts the assignment, {% data variables.product.prodname_classroom %} creates the assignment repository, and the Settings app automatically applies the settings from _.github/settings.yml_.
+
+Probot is a a project, framework, and collection of free apps to automate {% data variables.product.product_name %}. A Probot app can listen to repository events, like the creation of new commits, comments, and issues, and automatically respond to the event.
+
+For more information, see the [Probot website](https://probot.github.io) and the [Settings app website](https://probot.github.io/apps/settings/). For more information about {% data variables.product.prodname_github_apps %}, see "[About apps](/developers/apps/about-apps)."
+
+### Adding the Settings app to your organization
+
+After you install the Probot Settings app for your organization, the app will apply the settings that you define in _.github/settings.yml_ for any repository in your organization, including new assignment repositories that {% data variables.product.prodname_classroom %} creates.
+
+1. Navigate to the [Settings app page](https://github.com/apps/settings).
+1. Click **Install**, then click the organization that you use for {% data variables.product.prodname_classroom %}. Provide the app full access to all repositories owned by the organization.
+ 
+
+### Configuring default settings for an assignment repository
+
+1. Create a template repository that contains a _.github/settings.yml_ file. For a complete list of settings, see the [README](https://github.com/probot/settings#github-settings) for the `probot/settings` repository. For more information about using a template repository for starter code in {% data variables.product.prodname_classroom %}, see "[Create an assignment from a template repository](/education/manage-coursework-with-github-classroom/create-an-assignment-from-a-template-repository)."
+
+ {% warning %}
+
+ **Warning:** Do not define `collaborators` in the _.github/settings.yml_ file for your template repository. {% data variables.product.prodname_classroom %} automatically grants teachers and teaching assistants access to assignment repositories.
+
+ {% endwarning %}
+
+1. Create an assignment using the template repository containing _.github/settings.yml_ as the starter code. {% data reusables.classroom.for-more-information-about-assignment-creation %}
+
+The Probot Settings app for your organization will now apply the settings you define in _.github/settings.yml_ within the template repository to every assignment repository that {% data reusables.classroom.you-may-want-to-predefine-repository-settings %} creates for a student or team.
+
+### Further reading
+
+- [Probot apps](https://probot.github.io/apps/)
+- [Probot documentation](https://probot.github.io/docs/)
diff --git a/content/education/manage-coursework-with-github-classroom/connect-a-learning-management-system-to-github-classroom.md b/content/education/manage-coursework-with-github-classroom/connect-a-learning-management-system-to-github-classroom.md
new file mode 100644
index 0000000000..0ed3baed4f
--- /dev/null
+++ b/content/education/manage-coursework-with-github-classroom/connect-a-learning-management-system-to-github-classroom.md
@@ -0,0 +1,142 @@
+---
+title: Connect a learning management system to GitHub Classroom
+intro: You can configure an LTI-compliant learning management system (LMS) to connect to {% data variables.product.prodname_classroom %} so that you can import a roster for your classroom.
+versions:
+ free-pro-team: '*'
+redirect_from:
+ - /education/manage-coursework-with-github-classroom/configuring-a-learning-management-system-for-github-classroom
+ - /education/manage-coursework-with-github-classroom/connect-to-lms
+ - /education/manage-coursework-with-github-classroom/generate-lms-credentials
+ - /education/manage-coursework-with-github-classroom/setup-canvas
+ - /education/manage-coursework-with-github-classroom/setup-generic-lms
+ - /education/manage-coursework-with-github-classroom/setup-moodle
+---
+
+### About configuration of your LMS
+
+You can connect a learning management system (LMS) to {% data variables.product.prodname_classroom %}, and {% data variables.product.prodname_classroom %} can import a roster of student identifiers from the LMS. To connect your LMS to {% data variables.product.prodname_classroom %}, you must enter configuration credentials for {% data variables.product.prodname_classroom %} in your LMS.
+
+### Prerequisites
+
+To configure an LMS to connect to {% data variables.product.prodname_classroom %}, you must first create a classroom. For more information, see "[Manage classrooms](/education/manage-coursework-with-github-classroom/manage-classrooms#creating-a-classroom)."
+
+### Supported LMSes
+
+{% data variables.product.prodname_classroom %} supports import of roster data from LMSes that implement Learning Tools Interoperability (LTI) standards.
+
+- LTI version 1.0 and/or 1.1
+- LTI Names and Roles Provisioning 1.X
+
+Using LTI helps keep your information safe and secure. LTI is an industry-standard protocol and GitHub Classroom's use of LTI is certified by the Instructional Management System (IMS) Global Learning Consortium. For more information, see [Learning Tools Interoperability](https://www.imsglobal.org/activity/learning-tools-interoperability) and [About IMS Global Learning Consortium](http://www.imsglobal.org/aboutims.html) on the IMS Global Learning Consortium website.
+
+{% data variables.product.company_short %} has tested import of roster data from the following LMSes into {% data variables.product.prodname_classroom %}.
+
+- Canvas
+- Google Classroom
+- Moodle
+- Sakai
+
+Currently, {% data variables.product.prodname_classroom %} doesn't support import of roster data from Blackboard or Brightspace
+
+### Generating configuration credentials for your classroom
+
+{% data reusables.classroom.sign-into-github-classroom %}
+{% data reusables.classroom.click-classroom-in-list %}
+{% data reusables.classroom.click-students %}
+1. If your classroom already has a roster, you can either update the roster or delete the roster and create a new roster.
+ - For more information about deleting and creating a roster, see "[Deleting a roster for a classroom](/education/manage-coursework-with-github-classroom/manage-classrooms#deleting-a-roster-for-a-classroom)" and "[Creating a roster for your classroom](/education/manage-coursework-with-github-classroom/manage-classrooms#creating-a-roster-for-your-classroom)."
+ - For more information about updating a roster, see "[Adding students to the roster for your classroom](/education/manage-coursework-with-github-classroom/manage-classrooms#adding-students-to-the-roster-for-your-classroom)."
+1. In the list of LMSes, click your LMS. If your LMS is not supported, click **Other LMS**.
+ 
+1. Read about connecting your LMS, then click **Connect to _LMS_**.
+1. Copy the "Consumer Key", "Shared Secret", and "Launch URL" for the connection to the classroom.
+ 
+
+### Configuring a generic LMS
+
+You must configure the privacy settings for your LMS to allow external tools to receive roster information.
+
+1. Navigate to your LMS.
+1. Configure an external tool.
+1. Provide the configuration credentials you generated in {% data variables.product.prodname_classroom %}.
+ - Consumer key
+ - Shared secret
+ - Launch URL (sometimes called "tool URL" or similar)
+
+### Configuring Canvas
+
+You can configure {% data variables.product.prodname_classroom %} as an external app for Canvas to import roster data into your classroom. For more information about Canvas, see the [Canvas website](https://www.instructure.com/canvas/).
+
+1. Sign into [Canvas](https://www.instructure.com/canvas/#login).
+1. Select the Canvas course to integrate with {% data variables.product.prodname_classroom %}.
+1. In the left sidebar, click **Settings**.
+1. Click the **Apps** tab.
+1. Click **View app configurations**.
+1. Click **+App**.
+1. Select the **Configuration Type** drop-down menu, and click **By URL**.
+1. Paste the configuration credentials from {% data variables.product.prodname_classroom %}. For more information, see "[Generating configuration credentials for your classroom](#generating-configuration-credentials-for-your-classroom)."
+
+ | Field in Canvas app configuration | Value or setting |
+ | :- | :- |
+ | **Consumer Key** | Consumer key from {% data variables.product.prodname_classroom %} |
+ | **Shared Secret** | Shared secret from {% data variables.product.prodname_classroom %} |
+ | **Allow this tool to access the IMS Names and Role Provisioning Service** | Enabled |
+ | **Configuration URL** | Launch URL from {% data variables.product.prodname_classroom %} |
+
+ {% note %}
+
+ **Note**: If you don't see a checkbox in Canvas labeled "Allow this tool to access the IMS Names and Role Provisioning Service", then your Canvas administrator must contact Canvas support to enable membership service configuration for your Canvas account. Without enabling this feature, you won't be able to sync the roster from Canvas. For more information, see [How do I contact Canvas Support?](https://community.canvaslms.com/t5/Canvas-Basics-Guide/How-do-I-contact-Canvas-Support/ta-p/389767) on the Canvas website.
+
+ {% endnote %}
+
+1. Click **Submit**.
+1. In the left sidebar, click **Home**.
+1. To prompt Canvas to send a confirmation email, in the left sidebar, click **GitHub Classroom**. Follow the instructions in the email to finish linking {% data variables.product.prodname_classroom %}.
+
+### Configuring Moodle
+
+You can configure {% data variables.product.prodname_classroom %} as an activity for Moodle to import roster data into your classroom. For more information about Moodle, see the [Moodle website](https://moodle.org).
+
+You must be using Moodle version 3.0 or greater.
+
+1. Sign into [Moodle](https://moodle.org/login/index.php).
+1. Select the Moodle course to integrate with {% data variables.product.prodname_classroom %}.
+1. Click **Turn editing on**.
+1. Wherever you'd like {% data variables.product.prodname_classroom %} to be available in Moodle, click **Add an activity or resource**.
+1. Choose **External tool** and click **Add**.
+1. In the "Activity name" field, type "GitHub Classroom".
+1. In the **Preconfigured tool** field, to the right of the drop-down menu, click **+**.
+1. Under "External tool configuration", paste the configuration credentials from {% data variables.product.prodname_classroom %}. For more information, see "[Generating configuration credentials for your classroom](#generating-configuration-credentials-for-your-classroom)."
+
+ | Field in Moodle app configuration | Value or setting |
+ | :- | :- |
+ | **Tool name** | {% data variables.product.prodname_classroom %} - _YOUR CLASSROOM NAME_
**Note**: You can use any name, but we suggest this value for clarity. |
+ | **Tool URL** | Launch URL from {% data variables.product.prodname_classroom %} |
+ | **LTI version** | LTI 1.0/1.1 |
+ | **Default launch container** | New window |
+ | **Consumer key** | Consumer key from {% data variables.product.prodname_classroom %} |
+ | **Shared secret** | Shared secret from {% data variables.product.prodname_classroom %} |
+
+1. Scroll to and click **Services**.
+1. To the right of "IMS LTI Names and Role Provisioning", select the drop-down menu and click **Use this service to retrieve members' information as per privacy settings**.
+1. Scroll to and click **Privacy**.
+1. To the right of **Share launcher's name with tool** and **Share launcher's email with tool**, select the drop-down menus to click **Always**.
+1. At the bottom of the page, click **Save changes**.
+1. In the **Preconfigure tool** menu, click **GitHub Classroom - _YOUR CLASSROOM NAME_**.
+1. Under "Common module settings", to the right of "Availability", select the drop-down menu and click **Hide from students**.
+1. At the bottom of the page, click **Save and return to course**.
+1. Navigate to anywhere you chose to display {% data variables.product.prodname_classroom %}, and click the {% data variables.product.prodname_classroom %} activity.
+
+### Importing a roster from your LMS
+
+For more information about importing the roster from your LMS into {% data variables.product.prodname_classroom %}, see "[Manage classrooms](/education/manage-coursework-with-github-classroom/manage-classrooms#creating-a-roster-for-your-classroom)."
+
+### Disconnecting your LMS
+
+{% data reusables.classroom.sign-into-github-classroom %}
+{% data reusables.classroom.click-classroom-in-list %}
+{% data reusables.classroom.click-settings %}
+1. Under "Connect to a learning management system (LMS)", click **Connection Settings**.
+ 
+1. Under "Delete Connection to your learning management system", click **Disconnect from your learning management system**.
+ 
diff --git a/content/education/manage-coursework-with-github-classroom/create-a-group-assignment.md b/content/education/manage-coursework-with-github-classroom/create-a-group-assignment.md
new file mode 100644
index 0000000000..2b42b006f8
--- /dev/null
+++ b/content/education/manage-coursework-with-github-classroom/create-a-group-assignment.md
@@ -0,0 +1,145 @@
+---
+title: Create a group assignment
+intro: 'You can create a collaborative assignment for teams of students who participate in your course.'
+versions:
+ free-pro-team: '*'
+redirect_from:
+ - /education/manage-coursework-with-github-classroom/create-group-assignments
+---
+
+### About group assignments
+
+{% data reusables.classroom.assignments-group-definition %} Students can work together on a group assignment in a shared repository, like a team of professional developers.
+
+When a student accepts a group assignment, the student can create a new team or join an existing team. {% data variables.product.prodname_classroom %} saves the teams for an assignment as a set. You can name the set of teams for a specific assignment when you create the assignment, and you can reuse that set of teams for a later assignment.
+
+{% data reusables.classroom.classroom-creates-group-repositories %}
+
+{% data reusables.classroom.about-assignments %}
+
+You can decide how many teams one assignment can have, and how many members each team can have. Each team that a student creates for an assignment is a team within your organization on {% data variables.product.product_name %}. The visibility of the team is secret. Teams that you create on {% data variables.product.product_name %} will not appear in {% data variables.product.prodname_classroom %}. For more information, see "[About teams](/github/setting-up-and-managing-organizations-and-teams/about-teams)."
+
+For a video demonstration of the creation of a group assignment, see "[Basics of setting up {% data variables.product.prodname_classroom %}](/education/manage-coursework-with-github-classroom/basics-of-setting-up-github-classroom)."
+
+### Prerequisites
+
+{% data reusables.classroom.assignments-classroom-prerequisite %}
+
+### Creating an assignment
+
+{% data reusables.classroom.assignments-guide-create-the-assignment %}
+
+### Setting up the basics for an assignment
+
+Name your assignment, decide whether to assign a deadline, define teams, and choose the visibility of assignment repositories.
+
+- [Naming an assignment](#naming-an-assignment)
+- [Assigning a deadline for an assignment](#assigning-a-deadline-for-an-assignment)
+- [Choosing an assignment type](#choosing-an-assignment-type)
+- [Defining teams for an assignment](#defining-teams-for-an-assignment)
+- [Choosing a visibility for assignment repositories](#choosing-a-visibility-for-assignment-repositories)
+
+#### Naming an assignment
+
+For a group assignment, {% data variables.product.prodname_classroom %} names repositories by the repository prefix and the name of the team. By default, the repository prefix is the assignment title. For example, if you name an assignment "assignment-1" and the team's name on {% data variables.product.product_name %} is "student-team", the name of the assignment repository for members of the team will be `assignment-1-student-team`.
+
+{% data reusables.classroom.assignments-type-a-title %}
+
+#### Assigning a deadline for an assignment
+
+{% data reusables.classroom.assignments-guide-assign-a-deadline %}
+
+#### Choosing an assignment type
+
+Under "Individual or group assignment", select the drop-down menu, then click **Group assignment**. You can't change the assignment type after you create the assignment. If you'd rather create a individual assignment, see "[Create an individual assignment](/education/manage-coursework-with-github-classroom/create-an-individual-assignment)."
+
+#### Defining teams for an assignment
+
+If you've already created a group assignment for the classroom, you can reuse a set of teams for the new assignment. To create a new set with the teams that your students create for the assignment, type the name for the set. Optionally, type the maximum number of team members and total teams.
+
+{% tip %}
+
+**Tips**:
+
+- We recommend including details about the set of teams in the name for the set. For example, if you want to use the set of teams for one assignment, name the set after the assignment. If you want to reuse the set throughout a semester or course, name the set after the semester or course.
+
+- If you'd like to assign students to a specific team, give your students a name for the team and provide a list of members.
+
+{% endtip %}
+
+
+
+#### Choosing a visibility for assignment repositories
+
+{% data reusables.classroom.assignments-guide-choose-visibility %}
+
+{% data reusables.classroom.assignments-guide-click-continue-after-basics %}
+
+### Adding starter code and configuring a development environment
+
+{% data reusables.classroom.assignments-guide-intro-for-environment %}
+
+- [Choosing a template repository](#choosing-a-template-repository)
+- [Choosing an online integrated development environment (IDE)](#choosing-an-online-integrated-development-environment-ide)
+
+#### Choosing a template repository
+
+By default, a new assignment will create an empty repository for each team that a student creates. {% data reusables.classroom.you-can-choose-a-template-repository %} For more information about template repositories, see "[Creating a template repository](/github/creating-cloning-and-archiving-repositories/creating-a-template-repository)."
+
+{% data reusables.classroom.assignments-guide-choose-template-repository %}
+
+#### Choosing an online integrated development environment (IDE)
+
+{% data reusables.classroom.about-online-ides %} For more information, see "[Integrate {% data variables.product.prodname_classroom %} with an IDE](/education/manage-coursework-with-github-classroom/integrate-github-classroom-with-an-ide)."
+
+{% data reusables.classroom.assignments-guide-choose-an-online-ide %}
+
+{% data reusables.classroom.assignments-guide-click-continue-after-starter-code-and-feedback %}
+
+### Providing feedback
+
+Optionally, you can automatically grade assignments and create a space for discussing each submission with the team.
+
+- [Testing assignments automatically](#testing-assignments-automatically)
+- [Preventing changes to important files](#preventing-changes-to-important-files)
+- [Creating a pull request for feedback](#creating-a-pull-request-for-feedback)
+
+#### Testing assignments automatically
+
+{% data reusables.classroom.assignments-guide-using-autograding %}
+
+#### Preventing changes to important files
+
+{% data reusables.classroom.assignments-guide-prevent-changes %}
+
+#### Creating a pull request for feedback
+
+{% data reusables.classroom.you-can-create-a-pull-request-for-feedback %}
+
+{% data reusables.classroom.assignments-guide-create-review-pull-request %}
+
+{% data reusables.classroom.assignments-guide-click-create-assignment-button %}
+
+### Inviting students to an assignment
+
+{% data reusables.classroom.assignments-guide-invite-students-to-assignment %}
+
+You can see the teams that are working on or have submitted an assignment in the **Teams** tab for the assignment. {% data reusables.classroom.assignments-to-prevent-submission %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Forces the removal of a self-hosted runner from an enterprise. You can use this endpoint to completely remove the runner when the machine you were using no longer exists.
\nYou must authenticate using an access token with the admin:enterprise scope to use this endpoint.
The slug version of the enterprise name. You can also substitute this value with the enterprise id.
" + }, + { + "name": "phrase", + "description": "A search phrase. For more information, see [Searching the audit log](https://docs.github.com/github/setting-up-and-managing-organizations-and-teams/reviewing-the-audit-log-for-your-organization#searching-the-audit-log).", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "descriptionHTML": "A search phrase. For more information, see Searching the audit log.
" + }, + { + "name": "include", + "description": "The event types to include:\n\n- `web` - returns web (non-Git) events\n- `git` - returns Git events\n- `all` - returns both web and Git events\n\nThe default is `web`.", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "web", + "git", + "all" + ] + }, + "descriptionHTML": "The event types to include:
\nweb - returns web (non-Git) eventsgit - returns Git eventsall - returns both web and Git eventsThe default is web.
A cursor, as given in the Link header. If specified, the query only searches for events after this cursor.
" + }, + { + "name": "before", + "description": "A cursor, as given in the [Link header](https://docs.github.com/rest/overview/resources-in-the-rest-api#link-header). If specified, the query only searches for events before this cursor.", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "descriptionHTML": "A cursor, as given in the Link header. If specified, the query only searches for events before this cursor.
" + }, + { + "name": "per_page", + "description": "Results per page (max 100)", + "in": "query", + "schema": { + "type": "integer", + "default": 30 + }, + "descriptionHTML": "Results per page (max 100)
" + } + ], + "x-codeSamples": [ + { + "lang": "Shell", + "source": "curl \\\n -H \"Accept: application/vnd.github.v3+json\" \\\n https://api.github.com/enterprises/ENTERPRISE/audit-log", + "html": "curl \\\n -H \"Accept: application/vnd.github.v3+json\" \\\n https://api.github.com/enterprises/ENTERPRISE/audit-log"
+ },
+ {
+ "lang": "JavaScript",
+ "source": "await octokit.request('GET /enterprises/{enterprise}/audit-log', {\n enterprise: 'enterprise'\n})",
+ "html": "await octokit.request('GET /enterprises/{enterprise}/audit-log', {\n enterprise: 'enterprise'\n})\n"
+ }
+ ],
+ "summary": "Get the audit log for an enterprise",
+ "description": "**Note:** The audit log REST API is currently in beta and is subject to change. To join the beta, talk to your services or sales contact at GitHub.\n\nGets the audit log for an enterprise. To use this endpoint, you must be an enterprise admin, and you must use an access token with the `admin:enterprise` scope.",
+ "operationId": "audit-log/get-audit-log",
+ "tags": [
+ "audit-log"
+ ],
+ "externalDocs": {
+ "description": "API method documentation",
+ "url": "https://docs.github.com/rest/reference/enterprise-admin#get-the-audit-log-for-an-enterprise"
+ },
+ "x-github": {
+ "githubCloudOnly": true,
+ "enabledForGitHubApps": false,
+ "previews": [],
+ "category": "enterprise-admin",
+ "subcategory": "audit-log"
+ },
+ "slug": "get-the-audit-log-for-an-enterprise",
+ "category": "enterprise-admin",
+ "categoryLabel": "Enterprise admin",
+ "subcategory": "audit-log",
+ "subcategoryLabel": "Audit log",
+ "notes": [],
+ "bodyParameters": [],
+ "descriptionHTML": "Note: The audit log REST API is currently in beta and is subject to change. To join the beta, talk to your services or sales contact at GitHub.
\nGets the audit log for an enterprise. To use this endpoint, you must be an enterprise admin, and you must use an access token with the admin:enterprise scope.
[\n {\n \"@timestamp\": 1606929874512,\n \"action\": \"team.add_member\",\n \"actor\": \"octocat\",\n \"created_at\": 1606929874512,\n \"org\": \"octo-corp\",\n \"team\": \"octo-corp/example-team\",\n \"user\": \"monalisa\"\n },\n {\n \"@timestamp\": 1606507117008,\n \"action\": \"org.create\",\n \"actor\": \"octocat\",\n \"created_at\": 1606507117008,\n \"org\": \"octocat-test-org\"\n },\n {\n \"@timestamp\": 1605719148837,\n \"action\": \"repo.destroy\",\n \"actor\": \"monalisa\",\n \"created_at\": 1605719148837,\n \"org\": \"mona-org\",\n \"repo\": \"mona-org/mona-test-repo\",\n \"visibility\": \"private\"\n }\n]\n"
+ }
+ ]
+ },
{
"verb": "get",
"requestPath": "/enterprises/{enterprise}/settings/billing/actions",
@@ -14225,6 +14342,120 @@
"bodyParameters": [],
"descriptionHTML": "Removes a repository from an organization secret when the visibility for repository access is set to selected. The visibility is set when you Create or update an organization secret. You must authenticate using an access token with the admin:org scope to use this endpoint. GitHub Apps must have the secrets organization permission to use this endpoint.
A search phrase. For more information, see Searching the audit log.
" + }, + { + "name": "include", + "description": "The event types to include:\n\n- `web` - returns web (non-Git) events\n- `git` - returns Git events\n- `all` - returns both web and Git events\n\nThe default is `web`.", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "web", + "git", + "all" + ] + }, + "descriptionHTML": "The event types to include:
\nweb - returns web (non-Git) eventsgit - returns Git eventsall - returns both web and Git eventsThe default is web.
A cursor, as given in the Link header. If specified, the query only searches for events after this cursor.
" + }, + { + "name": "before", + "description": "A cursor, as given in the [Link header](https://docs.github.com/rest/overview/resources-in-the-rest-api#link-header). If specified, the query only searches for events before this cursor.", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "descriptionHTML": "A cursor, as given in the Link header. If specified, the query only searches for events before this cursor.
" + }, + { + "name": "per_page", + "description": "Results per page (max 100)", + "in": "query", + "schema": { + "type": "integer", + "default": 30 + }, + "descriptionHTML": "Results per page (max 100)
" + } + ], + "x-codeSamples": [ + { + "lang": "Shell", + "source": "curl \\\n -H \"Accept: application/vnd.github.v3+json\" \\\n https://api.github.com/orgs/ORG/audit-log", + "html": "curl \\\n -H \"Accept: application/vnd.github.v3+json\" \\\n https://api.github.com/orgs/ORG/audit-log"
+ },
+ {
+ "lang": "JavaScript",
+ "source": "await octokit.request('GET /orgs/{org}/audit-log', {\n org: 'org'\n})",
+ "html": "await octokit.request('GET /orgs/{org}/audit-log', {\n org: 'org'\n})\n"
+ }
+ ],
+ "summary": "Get the audit log for an organization",
+ "description": "**Note:** The audit log REST API is currently in beta and is subject to change. To join the beta, talk to your services or sales contact at GitHub.\n\nGets the audit log for an organization. For more information, see \"[Reviewing the audit log for your organization](https://docs.github.com/github/setting-up-and-managing-organizations-and-teams/reviewing-the-audit-log-for-your-organization).\"\n\nTo use this endpoint, you must be an organization owner, and you must use an access token with the `admin:org` scope. GitHub Apps must have the `organization_administration` read permission to use this endpoint.",
+ "operationId": "orgs/get-audit-log",
+ "tags": [
+ "orgs"
+ ],
+ "externalDocs": {
+ "description": "API method documentation",
+ "url": "https://docs.github.com/rest/reference/orgs#get-the-audit-log-for-an-organization"
+ },
+ "x-github": {
+ "githubCloudOnly": true,
+ "enabledForGitHubApps": true,
+ "previews": [],
+ "category": "orgs",
+ "subcategory": null
+ },
+ "slug": "get-the-audit-log-for-an-organization",
+ "category": "orgs",
+ "categoryLabel": "Orgs",
+ "notes": [],
+ "bodyParameters": [],
+ "descriptionHTML": "Note: The audit log REST API is currently in beta and is subject to change. To join the beta, talk to your services or sales contact at GitHub.
\nGets the audit log for an organization. For more information, see \"Reviewing the audit log for your organization.\"
\nTo use this endpoint, you must be an organization owner, and you must use an access token with the admin:org scope. GitHub Apps must have the organization_administration read permission to use this endpoint.
[\n {\n \"@timestamp\": 1606929874512,\n \"action\": \"team.add_member\",\n \"actor\": \"octocat\",\n \"created_at\": 1606929874512,\n \"org\": \"octo-corp\",\n \"team\": \"octo-corp/example-team\",\n \"user\": \"monalisa\"\n },\n {\n \"@timestamp\": 1606507117008,\n \"action\": \"org.create\",\n \"actor\": \"octocat\",\n \"created_at\": 1606507117008,\n \"org\": \"octocat-test-org\"\n },\n {\n \"@timestamp\": 1605719148837,\n \"action\": \"repo.destroy\",\n \"actor\": \"monalisa\",\n \"created_at\": 1605719148837,\n \"org\": \"mona-org\",\n \"repo\": \"mona-org/mona-test-repo\",\n \"visibility\": \"private\"\n }\n]\n"
+ }
+ ]
+ },
{
"verb": "get",
"requestPath": "/orgs/{org}/blocks",
diff --git a/lib/rest/static/dereferenced/api.github.com.deref.json b/lib/rest/static/dereferenced/api.github.com.deref.json
index 873643c5bc..7dbc79d269 100644
--- a/lib/rest/static/dereferenced/api.github.com.deref.json
+++ b/lib/rest/static/dereferenced/api.github.com.deref.json
@@ -17051,6 +17051,254 @@
}
}
},
+ "/enterprises/{enterprise}/audit-log": {
+ "get": {
+ "summary": "Get the audit log for an enterprise",
+ "description": "**Note:** The audit log REST API is currently in beta and is subject to change. To join the beta, talk to your services or sales contact at GitHub.\n\nGets the audit log for an enterprise. To use this endpoint, you must be an enterprise admin, and you must use an access token with the `admin:enterprise` scope.",
+ "operationId": "audit-log/get-audit-log",
+ "tags": [
+ "audit-log"
+ ],
+ "externalDocs": {
+ "description": "API method documentation",
+ "url": "https://docs.github.com/rest/reference/enterprise-admin#get-the-audit-log-for-an-enterprise"
+ },
+ "parameters": [
+ {
+ "name": "enterprise",
+ "description": "The slug version of the enterprise name. You can also substitute this value with the enterprise id.",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "phrase",
+ "description": "A search phrase. For more information, see [Searching the audit log](https://docs.github.com/github/setting-up-and-managing-organizations-and-teams/reviewing-the-audit-log-for-your-organization#searching-the-audit-log).",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "include",
+ "description": "The event types to include:\n\n- `web` - returns web (non-Git) events\n- `git` - returns Git events\n- `all` - returns both web and Git events\n\nThe default is `web`.",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "web",
+ "git",
+ "all"
+ ]
+ }
+ },
+ {
+ "name": "after",
+ "description": "A cursor, as given in the [Link header](https://docs.github.com/rest/overview/resources-in-the-rest-api#link-header). If specified, the query only searches for events after this cursor.",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "before",
+ "description": "A cursor, as given in the [Link header](https://docs.github.com/rest/overview/resources-in-the-rest-api#link-header). If specified, the query only searches for events before this cursor.",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "per_page",
+ "description": "Results per page (max 100)",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "default": 30
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@timestamp": {
+ "type": "integer",
+ "description": "The time the audit log event occurred, given as a [Unix timestamp](http://en.wikipedia.org/wiki/Unix_time)."
+ },
+ "action": {
+ "type": "string",
+ "description": "The name of the action that was performed, for example `user.login` or `repo.create`."
+ },
+ "active": {
+ "type": "boolean"
+ },
+ "active_was": {
+ "type": "boolean"
+ },
+ "actor": {
+ "type": "string",
+ "description": "The actor who performed the action."
+ },
+ "blocked_user": {
+ "type": "string",
+ "description": "The username of the account being blocked."
+ },
+ "business": {
+ "type": "string"
+ },
+ "config": {
+ "type": "array"
+ },
+ "config_was": {
+ "type": "array"
+ },
+ "content_type": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "integer",
+ "description": "The time the audit log event was recorded, given as a [Unix timestamp](http://en.wikipedia.org/wiki/Unix_time)."
+ },
+ "deploy_key_fingerprint": {
+ "type": "string"
+ },
+ "emoji": {
+ "type": "string"
+ },
+ "events": {
+ "type": "array"
+ },
+ "events_were": {
+ "type": "array"
+ },
+ "explanation": {
+ "type": "string"
+ },
+ "fingerprint": {
+ "type": "string"
+ },
+ "hook_id": {
+ "type": "integer"
+ },
+ "limited_availability": {
+ "type": "boolean"
+ },
+ "message": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "old_user": {
+ "type": "string"
+ },
+ "openssh_public_key": {
+ "type": "string"
+ },
+ "org": {
+ "type": "string"
+ },
+ "previous_visibility": {
+ "type": "string"
+ },
+ "read_only": {
+ "type": "boolean"
+ },
+ "repo": {
+ "type": "string",
+ "description": "The name of the repository."
+ },
+ "repository": {
+ "type": "string",
+ "description": "The name of the repository."
+ },
+ "repository_public": {
+ "type": "boolean"
+ },
+ "target_login": {
+ "type": "string"
+ },
+ "team": {
+ "type": "string"
+ },
+ "transport_protocol": {
+ "type": "integer",
+ "description": "The type of protocol (for example, HTTP or SSH) used to transfer Git data."
+ },
+ "transport_protocol_name": {
+ "type": "string",
+ "description": "A human readable name for the protocol (for example, HTTP or SSH) used to transfer Git data."
+ },
+ "user": {
+ "type": "string",
+ "description": "The user that was affected by the action performed (if available)."
+ },
+ "visibility": {
+ "type": "string",
+ "description": "The repository visibility, for example `public` or `private`."
+ }
+ }
+ }
+ },
+ "examples": {
+ "default": {
+ "value": [
+ {
+ "@timestamp": 1606929874512,
+ "action": "team.add_member",
+ "actor": "octocat",
+ "created_at": 1606929874512,
+ "org": "octo-corp",
+ "team": "octo-corp/example-team",
+ "user": "monalisa"
+ },
+ {
+ "@timestamp": 1606507117008,
+ "action": "org.create",
+ "actor": "octocat",
+ "created_at": 1606507117008,
+ "org": "octocat-test-org"
+ },
+ {
+ "@timestamp": 1605719148837,
+ "action": "repo.destroy",
+ "actor": "monalisa",
+ "created_at": 1605719148837,
+ "org": "mona-org",
+ "repo": "mona-org/mona-test-repo",
+ "visibility": "private"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "x-github": {
+ "githubCloudOnly": true,
+ "enabledForGitHubApps": false,
+ "previews": [
+
+ ],
+ "category": "enterprise-admin",
+ "subcategory": "audit-log"
+ }
+ }
+ },
"/enterprises/{enterprise}/settings/billing/actions": {
"get": {
"summary": "Get GitHub Actions billing for an enterprise",
@@ -49105,6 +49353,253 @@
}
}
},
+ "/orgs/{org}/audit-log": {
+ "get": {
+ "summary": "Get the audit log for an organization",
+ "description": "**Note:** The audit log REST API is currently in beta and is subject to change. To join the beta, talk to your services or sales contact at GitHub.\n\nGets the audit log for an organization. For more information, see \"[Reviewing the audit log for your organization](https://docs.github.com/github/setting-up-and-managing-organizations-and-teams/reviewing-the-audit-log-for-your-organization).\"\n\nTo use this endpoint, you must be an organization owner, and you must use an access token with the `admin:org` scope. GitHub Apps must have the `organization_administration` read permission to use this endpoint.",
+ "operationId": "orgs/get-audit-log",
+ "tags": [
+ "orgs"
+ ],
+ "externalDocs": {
+ "description": "API method documentation",
+ "url": "https://docs.github.com/rest/reference/orgs#get-the-audit-log-for-an-organization"
+ },
+ "parameters": [
+ {
+ "name": "org",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "phrase",
+ "description": "A search phrase. For more information, see [Searching the audit log](https://docs.github.com/github/setting-up-and-managing-organizations-and-teams/reviewing-the-audit-log-for-your-organization#searching-the-audit-log).",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "include",
+ "description": "The event types to include:\n\n- `web` - returns web (non-Git) events\n- `git` - returns Git events\n- `all` - returns both web and Git events\n\nThe default is `web`.",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "web",
+ "git",
+ "all"
+ ]
+ }
+ },
+ {
+ "name": "after",
+ "description": "A cursor, as given in the [Link header](https://docs.github.com/rest/overview/resources-in-the-rest-api#link-header). If specified, the query only searches for events after this cursor.",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "before",
+ "description": "A cursor, as given in the [Link header](https://docs.github.com/rest/overview/resources-in-the-rest-api#link-header). If specified, the query only searches for events before this cursor.",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "per_page",
+ "description": "Results per page (max 100)",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "default": 30
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@timestamp": {
+ "type": "integer",
+ "description": "The time the audit log event occurred, given as a [Unix timestamp](http://en.wikipedia.org/wiki/Unix_time)."
+ },
+ "action": {
+ "type": "string",
+ "description": "The name of the action that was performed, for example `user.login` or `repo.create`."
+ },
+ "active": {
+ "type": "boolean"
+ },
+ "active_was": {
+ "type": "boolean"
+ },
+ "actor": {
+ "type": "string",
+ "description": "The actor who performed the action."
+ },
+ "blocked_user": {
+ "type": "string",
+ "description": "The username of the account being blocked."
+ },
+ "business": {
+ "type": "string"
+ },
+ "config": {
+ "type": "array"
+ },
+ "config_was": {
+ "type": "array"
+ },
+ "content_type": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "integer",
+ "description": "The time the audit log event was recorded, given as a [Unix timestamp](http://en.wikipedia.org/wiki/Unix_time)."
+ },
+ "deploy_key_fingerprint": {
+ "type": "string"
+ },
+ "emoji": {
+ "type": "string"
+ },
+ "events": {
+ "type": "array"
+ },
+ "events_were": {
+ "type": "array"
+ },
+ "explanation": {
+ "type": "string"
+ },
+ "fingerprint": {
+ "type": "string"
+ },
+ "hook_id": {
+ "type": "integer"
+ },
+ "limited_availability": {
+ "type": "boolean"
+ },
+ "message": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "old_user": {
+ "type": "string"
+ },
+ "openssh_public_key": {
+ "type": "string"
+ },
+ "org": {
+ "type": "string"
+ },
+ "previous_visibility": {
+ "type": "string"
+ },
+ "read_only": {
+ "type": "boolean"
+ },
+ "repo": {
+ "type": "string",
+ "description": "The name of the repository."
+ },
+ "repository": {
+ "type": "string",
+ "description": "The name of the repository."
+ },
+ "repository_public": {
+ "type": "boolean"
+ },
+ "target_login": {
+ "type": "string"
+ },
+ "team": {
+ "type": "string"
+ },
+ "transport_protocol": {
+ "type": "integer",
+ "description": "The type of protocol (for example, HTTP or SSH) used to transfer Git data."
+ },
+ "transport_protocol_name": {
+ "type": "string",
+ "description": "A human readable name for the protocol (for example, HTTP or SSH) used to transfer Git data."
+ },
+ "user": {
+ "type": "string",
+ "description": "The user that was affected by the action performed (if available)."
+ },
+ "visibility": {
+ "type": "string",
+ "description": "The repository visibility, for example `public` or `private`."
+ }
+ }
+ }
+ },
+ "examples": {
+ "default": {
+ "value": [
+ {
+ "@timestamp": 1606929874512,
+ "action": "team.add_member",
+ "actor": "octocat",
+ "created_at": 1606929874512,
+ "org": "octo-corp",
+ "team": "octo-corp/example-team",
+ "user": "monalisa"
+ },
+ {
+ "@timestamp": 1606507117008,
+ "action": "org.create",
+ "actor": "octocat",
+ "created_at": 1606507117008,
+ "org": "octocat-test-org"
+ },
+ {
+ "@timestamp": 1605719148837,
+ "action": "repo.destroy",
+ "actor": "monalisa",
+ "created_at": 1605719148837,
+ "org": "mona-org",
+ "repo": "mona-org/mona-test-repo",
+ "visibility": "private"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "x-github": {
+ "githubCloudOnly": true,
+ "enabledForGitHubApps": true,
+ "previews": [
+
+ ],
+ "category": "orgs",
+ "subcategory": null
+ }
+ }
+ },
"/orgs/{org}/blocks": {
"get": {
"summary": "List users blocked by an organization",
diff --git a/lib/rewrite-local-links.js b/lib/rewrite-local-links.js
index 4854a739cd..2cd18221c0 100644
--- a/lib/rewrite-local-links.js
+++ b/lib/rewrite-local-links.js
@@ -2,6 +2,7 @@ const externalRedirects = Object.keys(require('./redirects/external-sites'))
const pathUtils = require('./path-utils')
const assert = require('assert')
const nonEnterpriseDefaultVersion = require('./non-enterprise-default-version')
+const supportedPlans = Object.values(require('./all-versions')).map(v => v.plan)
// Content authors write links like `/some/article/path`, but they need to be
// rewritten on the fly to match the current language and page version
@@ -24,11 +25,21 @@ function getNewHref (link, languageCode, version) {
// e.g. `/contact` should not be replaced with `/en/contact`
if (externalRedirects.includes(href)) return
+ let newHref
+
+ // If the link has a hardcoded plan name in it (e.g., /enterprise-server/rest/reference/oauth-authorizations),
+ // only rewrite it with a language code
+ if (supportedPlans.includes(href.split('/')[1])) {
+ newHref = pathUtils.getPathWithLanguage(href, languageCode)
+ }
+
// If link is dotcom-only, just get the language code
// Otherwise, get the versioned path with language code
- const newHref = link.hasClass('dotcom-only')
- ? pathUtils.getVersionedPathWithLanguage(href, nonEnterpriseDefaultVersion, languageCode)
- : pathUtils.getVersionedPathWithLanguage(href, version, languageCode)
+ if (!newHref) {
+ newHref = link.hasClass('dotcom-only')
+ ? pathUtils.getVersionedPathWithLanguage(href, nonEnterpriseDefaultVersion, languageCode)
+ : pathUtils.getVersionedPathWithLanguage(href, version, languageCode)
+ }
if (href !== newHref) link.attr('href', newHref)
}
diff --git a/lib/site-data.js b/lib/site-data.js
index 7c4ba0c988..bdd47f1ce7 100755
--- a/lib/site-data.js
+++ b/lib/site-data.js
@@ -2,12 +2,12 @@ const path = require('path')
const flat = require('flat')
const { get, set } = require('lodash')
const languages = require('./languages')
-const dataDirectory = require('@github-docs/data-directory')
+const dataDirectory = require('./data-directory')
const encodeBracketedParentheticals = require('./encode-bracketed-parentheticals')
-const loadSiteDataFromDir = dir => ({
+const loadSiteDataFromDir = async dir => ({
site: {
- data: dataDirectory(path.join(dir, 'data'), {
+ data: await dataDirectory(path.join(dir, 'data'), {
preprocess: dataString =>
encodeBracketedParentheticals(dataString.trimEnd()),
ignorePatterns: [/README\.md$/]
@@ -18,7 +18,7 @@ const loadSiteDataFromDir = dir => ({
module.exports = async function loadSiteData () {
// load english site data
const siteData = {
- en: loadSiteDataFromDir(languages.en.dir)
+ en: await loadSiteDataFromDir(languages.en.dir)
}
// load and add other language data to siteData where keys match english keys,
@@ -26,7 +26,7 @@ module.exports = async function loadSiteData () {
const englishKeys = Object.keys(flat(siteData.en))
for (const language of Object.values(languages)) {
if (language.code === 'en') continue
- const data = loadSiteDataFromDir(language.dir)
+ const data = await loadSiteDataFromDir(language.dir)
for (const key of englishKeys) {
set(
siteData,
diff --git a/lib/warm-server.js b/lib/warm-server.js
index b123113051..36cf5e7416 100644
--- a/lib/warm-server.js
+++ b/lib/warm-server.js
@@ -4,6 +4,16 @@ const loadRedirects = require('./redirects/precompile')
const loadSiteData = require('./site-data')
const loadSiteTree = require('./site-tree')
+// Instrument these functions so that
+// it's wrapped in a timer that reports to Datadog
+const dog = {
+ loadPages: statsd.asyncTimer(loadPages, 'load_pages'),
+ loadPageMap: statsd.asyncTimer(loadPageMap, 'load_page_map'),
+ loadRedirects: statsd.asyncTimer(loadRedirects, 'load_redirects'),
+ loadSiteData: statsd.asyncTimer(loadSiteData, 'load_site_data'),
+ loadSiteTree: statsd.asyncTimer(loadSiteTree, 'load_site_tree')
+}
+
// For local caching
let pageList, pageMap, site, redirects, siteTree
@@ -32,21 +42,21 @@ async function warmServer () {
if (!pageList || !site) {
// Promise.all is used to load multiple things in parallel
[pageList, site] = await Promise.all([
- pageList || loadPages(),
- site || loadSiteData()
+ pageList || dog.loadPages(),
+ site || dog.loadSiteData()
])
}
if (!pageMap) {
- pageMap = await loadPageMap(pageList)
+ pageMap = await dog.loadPageMap(pageList)
}
if (!redirects) {
- redirects = await loadRedirects(pageList, pageMap)
+ redirects = await dog.loadRedirects(pageList, pageMap)
}
if (!siteTree) {
- siteTree = await loadSiteTree(pageMap, site, redirects)
+ siteTree = await dog.loadSiteTree(pageMap, site, redirects)
}
if (process.env.NODE_ENV !== 'test') {
@@ -58,7 +68,7 @@ async function warmServer () {
// Instrument the `warmServer` function so that
// it's wrapped in a timer that reports to Datadog
-const instrumentedWarmServer = statsd.asyncTimer(warmServer, 'warm_server')
+dog.warmServer = statsd.asyncTimer(warmServer, 'warm_server')
// We only want statistics if the priming needs to occur, so let's wrap the
// real method and return early [without statistics] whenever possible
@@ -68,5 +78,5 @@ module.exports = async function warmServerWrapper () {
return getWarmedCache()
}
- return instrumentedWarmServer()
+ return dog.warmServer()
}
diff --git a/middleware/categories-for-support-team.js b/middleware/categories-for-support-team.js
index 7a38fb0cde..4b550a566e 100644
--- a/middleware/categories-for-support-team.js
+++ b/middleware/categories-for-support-team.js
@@ -2,7 +2,7 @@
// to quickly search for Help articles by title and insert the link to
// the article into a reply to a customer.
const path = require('path')
-const fs = require('fs')
+const fs = require('fs').promises
const matter = require('gray-matter')
const dotcomDir = path.join(__dirname, '../content/github')
const dotcomIndex = path.join(dotcomDir, 'index.md')
@@ -10,24 +10,22 @@ const linkRegex = /{% (?:topic_)?link_in_list ?\/(.*?) ?%}/g
module.exports = async (req, res, next) => {
if (req.path !== '/categories.json') return next()
- const categories = generateCategories()
+ const categories = await generateCategories()
return res.json(categories)
}
-function generateCategories () {
+async function generateCategories () {
// get links included in dotcom index page.
// each link corresponds to a dotcom subdirectory
// example: getting-started-with-github
- const links = getLinks(fs.readFileSync(dotcomIndex, 'utf8'))
-
- const categories = []
+ const links = getLinks(await fs.readFile(dotcomIndex, 'utf8'))
// get links included in each subdir's index page
// these are links to articles
- links.forEach(link => {
+ const categories = await Promise.all(links.map(async link => {
const category = {}
const indexPath = getPath(link, 'index')
- const indexContents = fs.readFileSync(indexPath, 'utf8')
+ const indexContents = await fs.readFile(indexPath, 'utf8')
const { data, content } = matter(indexContents)
// get name from title frontmatter
@@ -36,29 +34,23 @@ function generateCategories () {
// get child article links
const articleLinks = getLinks(content)
- const publishedArticles = []
-
- articleLinks.forEach(articleLink => {
- const publishedArticle = {}
-
+ category.published_articles = (await Promise.all(articleLinks.map(async articleLink => {
// get title from frontmatter
const articlePath = getPath(link, articleLink)
- const articleContents = fs.readFileSync(articlePath, 'utf8')
+ const articleContents = await fs.readFile(articlePath, 'utf8')
const { data } = matter(articleContents)
// do not include map topics in list of published articles
if (data.mapTopic) return
- publishedArticle.title = data.title
- publishedArticle.slug = articleLink
+ return {
+ title: data.title,
+ slug: articleLink
+ }
+ }))).filter(Boolean)
- publishedArticles.push(publishedArticle)
- })
-
- category.published_articles = publishedArticles
-
- categories.push(category)
- })
+ return category
+ }))
return categories
}
diff --git a/middleware/csp.js b/middleware/csp.js
index a081f0490c..647448e928 100644
--- a/middleware/csp.js
+++ b/middleware/csp.js
@@ -2,47 +2,66 @@
// inline scripts and content from untrusted sources.
const { contentSecurityPolicy } = require('helmet')
+const isArchivedVersion = require('../lib/is-archived-version')
+const versionSatisfiesRange = require('../lib/version-satisfies-range')
-module.exports = contentSecurityPolicy({
- directives: {
- defaultSrc: ["'none'"],
- connectSrc: [
- "'self'",
- '*.algolia.net',
- '*.algolianet.com'
- ],
- fontSrc: [
- "'self'",
- 'data:',
- 'github-images.s3.amazonaws.com'
- ],
- imgSrc: [
- "'self'",
- 'github.githubassets.com',
- 'github-images.s3.amazonaws.com',
- 'octodex.github.com',
- 'placehold.it',
- '*.githubusercontent.com',
- 'github.com'
- ],
- objectSrc: [
- "'self'"
- ],
- scriptSrc: [
- "'self'",
- 'data:'
- ],
- frameSrc: [ // exceptions for GraphQL Explorer
- 'https://graphql-explorer.githubapp.com', // production env
- 'http://localhost:3000', // development env
- 'https://www.youtube-nocookie.com'
- ],
- styleSrc: [
- "'self'",
- "'unsafe-inline'"
- ],
- childSrc: [
- "'self'" // exception for search in deprecated GHE versions
- ]
+// module.exports = contentSecurityPolicy({
+module.exports = async (req, res, next) => {
+ const csp = {
+ directives: {
+ defaultSrc: ["'none'"],
+ connectSrc: [
+ "'self'",
+ '*.algolia.net',
+ '*.algolianet.com'
+ ],
+ fontSrc: [
+ "'self'",
+ 'data:',
+ 'github-images.s3.amazonaws.com'
+ ],
+ imgSrc: [
+ "'self'",
+ 'github.githubassets.com',
+ 'github-images.s3.amazonaws.com',
+ 'octodex.github.com',
+ 'placehold.it',
+ '*.githubusercontent.com',
+ 'github.com'
+ ],
+ objectSrc: [
+ "'self'"
+ ],
+ scriptSrc: [
+ "'self'",
+ 'data:'
+ ],
+ frameSrc: [ // exceptions for GraphQL Explorer
+ 'https://graphql-explorer.githubapp.com', // production env
+ 'http://localhost:3000', // development env
+ 'https://www.youtube-nocookie.com'
+ ],
+ styleSrc: [
+ "'self'",
+ "'unsafe-inline'"
+ ],
+ childSrc: [
+ "'self'" // exception for search in deprecated GHE versions
+ ]
+ }
}
-})
+
+ const { requestedVersion } = isArchivedVersion(req)
+
+ // Exception for Algolia instantsearch in deprecated Enterprise docs (Node.js era)
+ if (versionSatisfiesRange(requestedVersion, '<=2.19') && versionSatisfiesRange(requestedVersion, '>2.12')) {
+ csp.directives.scriptSrc.push("'unsafe-eval'")
+ }
+
+ // Exception for search in deprecated Enterprise docs <=2.12 (static site era)
+ if (versionSatisfiesRange(requestedVersion, '<=2.12')) {
+ csp.directives.scriptSrc.push("'unsafe-inline'")
+ }
+
+ return contentSecurityPolicy(csp)(req, res, next)
+}
diff --git a/package-lock.json b/package-lock.json
index b33963cf8b..e4cee8054d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2969,36 +2969,6 @@
}
}
},
- "@github-docs/data-directory": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@github-docs/data-directory/-/data-directory-1.2.0.tgz",
- "integrity": "sha512-hp+Ubwl8e77EdnR4OncSUIE7G/cMn9ENOo6ABy8FjqdYCbAWgb/79w7yXVebIV5P3q5r6KAAqPzHj1N5SSrBgw==",
- "requires": {
- "lodash": "^4.17.15",
- "walk-sync": "^2.0.2"
- },
- "dependencies": {
- "matcher-collection": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz",
- "integrity": "sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==",
- "requires": {
- "@types/minimatch": "^3.0.3",
- "minimatch": "^3.0.2"
- }
- },
- "walk-sync": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.1.0.tgz",
- "integrity": "sha512-KpH9Xw64LNSx7/UI+3guRZvJWlDxVA4+KKb/4puRoVrG8GkvZRxnF3vhxdjgpoKJGL2TVg1OrtkXIE/VuGPLHQ==",
- "requires": {
- "@types/minimatch": "^3.0.3",
- "ensure-posix-path": "^1.1.0",
- "matcher-collection": "^2.0.0"
- }
- }
- }
- },
"@github-docs/frontmatter": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@github-docs/frontmatter/-/frontmatter-1.3.1.tgz",
@@ -5301,7 +5271,7 @@
},
"agentkeepalive": {
"version": "2.2.0",
- "resolved": "http://registry.npmjs.org/agentkeepalive/-/agentkeepalive-2.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-2.2.0.tgz",
"integrity": "sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8="
},
"aggregate-error": {
@@ -5447,7 +5417,7 @@
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
@@ -5577,8 +5547,7 @@
"async": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
- "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==",
- "dev": true
+ "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
},
"async-each": {
"version": "1.0.3",
@@ -6825,7 +6794,7 @@
},
"brfs": {
"version": "1.6.1",
- "resolved": "http://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz",
+ "resolved": "https://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz",
"integrity": "sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ==",
"requires": {
"quote-stream": "^1.0.1",
@@ -9434,7 +9403,7 @@
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
- "integrity": "sha1-tKxAZIEH/c3PriQvQovqihTU8b8=",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"requires": {
"is-arrayish": "^0.2.1"
}
@@ -12578,7 +12547,7 @@
"dependencies": {
"mkdirp": {
"version": "0.3.0",
- "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
"integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4="
},
"nopt": {
@@ -17704,7 +17673,7 @@
},
"magic-string": {
"version": "0.22.5",
- "resolved": "http://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
"integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==",
"requires": {
"vlq": "^0.2.2"
diff --git a/package.json b/package.json
index bb0f35f588..545de58826 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,6 @@
"@babel/preset-env": "^7.12.7",
"@babel/preset-react": "^7.12.7",
"@babel/runtime": "^7.11.2",
- "@github-docs/data-directory": "^1.2.0",
"@github-docs/frontmatter": "^1.3.1",
"@graphql-inspector/core": "^2.3.0",
"@graphql-tools/load": "^6.2.5",
@@ -27,6 +26,7 @@
"@primer/css": "^15.1.0",
"@primer/octicons": "^11.0.0",
"algoliasearch": "^3.35.1",
+ "async": "^3.2.0",
"babel-loader": "^8.1.0",
"babel-preset-env": "^1.7.0",
"browser-date-formatter": "^3.0.3",
@@ -94,7 +94,6 @@
"devDependencies": {
"@actions/core": "^1.2.6",
"ajv": "^6.11.0",
- "async": "^3.2.0",
"await-sleep": "0.0.1",
"aws-sdk": "^2.610.0",
"babel-eslint": "^10.1.0",
@@ -181,4 +180,4 @@
"pre-push": "npm run prevent-pushes-to-main"
}
}
-}
\ No newline at end of file
+}
diff --git a/script/check-s3-images.js b/script/check-s3-images.js
index 00ef528b7c..6c83a4dc8a 100755
--- a/script/check-s3-images.js
+++ b/script/check-s3-images.js
@@ -12,6 +12,7 @@ const patterns = require('../lib/patterns')
const authenticateToAWS = require('../lib/authenticate-to-aws.js')
const readlineSync = require('readline-sync')
const { execSync } = require('child_process')
+const enterpriseServerVersions = Object.keys(allVersions).filter(v => v.startsWith('enterprise-server@'))
const uploadScript = path.join(process.cwd(), 'script/upload-images-to-s3.js')
// ignore the non-enterprise default version
@@ -51,7 +52,8 @@ async function main () {
page,
site: siteData,
currentVersion: version,
- currentLanguage: 'en'
+ currentLanguage: 'en',
+ enterpriseServerVersions
}
const rendered = await renderContent(page.markdown, context)
diff --git a/server.js b/server.js
index ce00b05169..744d1fa924 100644
--- a/server.js
+++ b/server.js
@@ -18,14 +18,8 @@ if (!module.parent) {
if (status === false) {
// If in a deployed environment, warm the server at the start
if (process.env.NODE_ENV === 'production') {
- // If in a true production environment, wait for the cache to be fully warmed.
- if (process.env.HEROKU_PRODUCTION_APP) {
- await warmServer()
- } else {
- // If not in a true production environment, don't wait for the cache to be fully warmed.
- // This avoids deployment timeouts in environments with slower servers.
- warmServer()
- }
+ // If in a production environment, wait for the cache to be fully warmed.
+ await warmServer()
}
// workaround for https://github.com/expressjs/express/issues/1101
diff --git a/tests/browser/browser.js b/tests/browser/browser.js
index 64fdb083ab..aca6b864a7 100644
--- a/tests/browser/browser.js
+++ b/tests/browser/browser.js
@@ -3,6 +3,8 @@ const sleep = require('await-sleep')
const querystring = require('querystring')
describe('homepage', () => {
+ jest.setTimeout(60 * 1000)
+
test('should be titled "GitHub Documentation"', async () => {
await page.goto('http://localhost:4001')
await expect(page.title()).resolves.toMatch('GitHub Documentation')
@@ -10,6 +12,8 @@ describe('homepage', () => {
})
describe('algolia browser search', () => {
+ jest.setTimeout(60 * 1000)
+
it('works on the homepage', async () => {
await page.goto('http://localhost:4001/en')
await page.click('#search-input-container input[type="search"]')
diff --git a/tests/content/category-pages.js b/tests/content/category-pages.js
index 3eb1214605..218301846e 100644
--- a/tests/content/category-pages.js
+++ b/tests/content/category-pages.js
@@ -42,7 +42,7 @@ describe('category pages', () => {
// Get links included in product index page.
// Each link corresponds to a product subdirectory (category).
// Example: "getting-started-with-github"
- const contents = fs.readFileSync(productIndex, 'utf8')
+ const contents = fs.readFileSync(productIndex, 'utf8') // TODO move to async
const { content } = matter(contents)
const productDir = path.dirname(productIndex)
@@ -50,6 +50,7 @@ describe('category pages', () => {
const categoryLinks = getLinks(content)
// Only include category directories, not standalone category files like content/actions/quickstart.md
.filter(link => fs.existsSync(getPath(productDir, link, 'index')))
+ // TODO this should move to async, but you can't asynchronously define tests with Jest...
// Map those to the Markdown file paths that represent that category page index
const categoryPaths = categoryLinks.map(link => getPath(productDir, link, 'index'))
diff --git a/tests/content/crowdin-config.js b/tests/content/crowdin-config.js
index 63301e0993..fd26e74b42 100644
--- a/tests/content/crowdin-config.js
+++ b/tests/content/crowdin-config.js
@@ -4,6 +4,8 @@ const ignoredPagePaths = config.files[0].ignore
const ignoredDataPaths = config.files[2].ignore
describe('crowdin.yml config file', () => {
+ jest.setTimeout(60 * 1000)
+
let pages
beforeAll(async (done) => {
pages = await loadPages()
diff --git a/tests/content/featured-links.js b/tests/content/featured-links.js
index c5b476153b..9829f1abd7 100644
--- a/tests/content/featured-links.js
+++ b/tests/content/featured-links.js
@@ -45,8 +45,9 @@ describe('featuredLinks', () => {
test('featured links respect versioning', async () => {
const $ = await getDOM(`/en/enterprise/${enterpriseServerReleases.latest}/user/packages`)
- const $featuredLinks = $('.featured-links a')
+ const $featuredLinks = $('.all-articles-list a')
expect($featuredLinks.length).toBeGreaterThan(2)
+ expect($featuredLinks.text().includes('Package client guides for GitHub Packages')).toBe(true)
// does not include dotcom-only links
expect($featuredLinks.text().includes('About GitHub Container Registry')).toBe(false)
expect($featuredLinks.text().includes('Getting started with GitHub Container Registry')).toBe(false)
diff --git a/tests/content/glossary.js b/tests/content/glossary.js
index 3e3aea66f9..3f278e5016 100644
--- a/tests/content/glossary.js
+++ b/tests/content/glossary.js
@@ -9,7 +9,10 @@ describe('glossaries', () => {
test('are broken into external, internal, and candidates', async () => {
const keys = Object.keys(glossaries)
- expect(keys).toEqual(['candidates', 'external', 'internal'])
+ expect(keys).toHaveLength(3)
+ expect(keys).toContain('candidates')
+ expect(keys).toContain('external')
+ expect(keys).toContain('internal')
})
test('every entry has a valid term', async () => {
diff --git a/tests/content/remove-liquid-statements.js b/tests/content/remove-liquid-statements.js
index 80fd021669..f94dd5ae5c 100644
--- a/tests/content/remove-liquid-statements.js
+++ b/tests/content/remove-liquid-statements.js
@@ -1,4 +1,4 @@
-const fs = require('fs')
+const fs = require('fs').promises
const path = require('path')
const cheerio = require('cheerio')
const matter = require('gray-matter')
@@ -35,8 +35,8 @@ function processFrontmatter (contents, file) {
}
describe('removing liquid statements only', () => {
- test('removes liquid statements that specify "greater than version to deprecate"', () => {
- let contents = fs.readFileSync(greaterThan, 'utf8')
+ test('removes liquid statements that specify "greater than version to deprecate"', async () => {
+ let contents = await fs.readFile(greaterThan, 'utf8')
contents = removeLiquidStatements(contents, versionToDeprecate, nextOldestVersion)
const $ = cheerio.load(contents)
expect($('.example1').text().trim()).toBe('Alpha')
@@ -57,8 +57,8 @@ Alpha\n\n{% else %}\n\nBravo\n\n{% if currentVersion ver_gt "enterprise-server@2
expect($('.example10').text().trim()).toBe('Alpha')
})
- test('removes liquid statements that specify "and greater than version to deprecate"', () => {
- let contents = fs.readFileSync(andGreaterThan1, 'utf8')
+ test('removes liquid statements that specify "and greater than version to deprecate"', async () => {
+ let contents = await fs.readFile(andGreaterThan1, 'utf8')
contents = removeLiquidStatements(contents, versionToDeprecate, nextOldestVersion)
const $ = cheerio.load(contents)
expect($('.example1').text().trim()).toBe('{% if currentVersion != "free-pro-team@latest" %}\n\nAlpha\n\n{% endif %}')
@@ -71,8 +71,8 @@ Alpha\n\n{% if currentVersion != "free-pro-team@latest" %}\n\nBravo\n\n{% endif
Alpha\n\n{% if currentVersion ver_gt "enterprise-server@2.16" %}\n\nBravo\n\n{% endif %}\n\n{% endif %}`)
})
- test('removes liquid statements that specify "and greater than version to deprecate" (alternate format)', () => {
- let contents = fs.readFileSync(andGreaterThan2, 'utf8')
+ test('removes liquid statements that specify "and greater than version to deprecate" (alternate format)', async () => {
+ let contents = await fs.readFile(andGreaterThan2, 'utf8')
contents = removeLiquidStatements(contents, versionToDeprecate, nextOldestVersion)
const $ = cheerio.load(contents)
expect($('.example1').text().trim()).toBe('{% if currentVersion ver_lt "enterprise-server@2.16" %}\n\nAlpha\n\n{% endif %}')
@@ -85,8 +85,8 @@ Alpha\n\n{% if currentVersion ver_lt "enterprise-server@2.16" %}\n\nBravo\n\n{%
Alpha\n\n{% if currentVersion != "free-pro-team@latest" %}\n\nBravo\n\n{% endif %}\n\n{% endif %}`)
})
- test('removes liquid statements that specify "not equals version to deprecate"', () => {
- let contents = fs.readFileSync(notEquals, 'utf8')
+ test('removes liquid statements that specify "not equals version to deprecate"', async () => {
+ let contents = await fs.readFile(notEquals, 'utf8')
contents = removeLiquidStatements(contents, versionToDeprecate, nextOldestVersion)
const $ = cheerio.load(contents)
expect($('.example1').text().trim()).toBe('Alpha')
@@ -103,8 +103,8 @@ Alpha\n\n{% endif %}`)
})
describe('removing liquid statements and content', () => {
- test('removes interior content and liquid statements that specify "equals version to deprecate"', () => {
- let contents = fs.readFileSync(equals, 'utf8')
+ test('removes interior content and liquid statements that specify "equals version to deprecate"', async () => {
+ let contents = await fs.readFile(equals, 'utf8')
contents = removeLiquidStatements(contents, versionToDeprecate, nextOldestVersion)
const $ = cheerio.load(contents)
expect($('.example1').text().trim()).toBe('')
@@ -117,8 +117,8 @@ Alpha\n\n{% else %}\n\nCharlie\n\n{% endif %}`)
expect($('.example6').text().trim()).toBe('Charlie\n\nBravo')
})
- test('removes interior content and liquid statements that specify "less than next oldest than version to deprecate"', () => {
- let contents = fs.readFileSync(lessThanNextOldest, 'utf8')
+ test('removes interior content and liquid statements that specify "less than next oldest than version to deprecate"', async () => {
+ let contents = await fs.readFile(lessThanNextOldest, 'utf8')
contents = removeLiquidStatements(contents, versionToDeprecate, nextOldestVersion)
const $ = cheerio.load(contents)
expect($('.example1').text().trim()).toBe('Alpha')
@@ -137,8 +137,8 @@ Charlie\n\n{% else %}\n\nDelta\n\n{% endif %}\n\nEcho`)
})
describe('updating frontmatter', () => {
- test('updates frontmatter versions Enterprise if set to greater-than-or-equal-to version to deprecate', () => {
- let contents = fs.readFileSync(frontmatter1, 'utf8')
+ test('updates frontmatter versions Enterprise if set to greater-than-or-equal-to version to deprecate', async () => {
+ let contents = await fs.readFile(frontmatter1, 'utf8')
contents = processFrontmatter(contents, frontmatter1)
const $ = cheerio.load(contents)
// console.log('foo')
@@ -147,8 +147,8 @@ describe('updating frontmatter', () => {
expect($.text().includes('enterprise-server: \'>=2.13\'')).toBe(false)
})
- test('updates frontmatter versions Enterprise if set to greater-than-or-equal-to next oldest version', () => {
- let contents = fs.readFileSync(frontmatter2, 'utf8')
+ test('updates frontmatter versions Enterprise if set to greater-than-or-equal-to next oldest version', async () => {
+ let contents = await fs.readFile(frontmatter2, 'utf8')
contents = processFrontmatter(contents, frontmatter2)
const $ = cheerio.load(contents)
expect($.text().includes('enterprise-server: \'*\'')).toBe(true)
@@ -157,8 +157,8 @@ describe('updating frontmatter', () => {
})
describe('whitespace', () => {
- test('does not add newlines when whitespace control is used', () => {
- let contents = fs.readFileSync(whitespace, 'utf8')
+ test('does not add newlines when whitespace control is used', async () => {
+ let contents = await fs.readFile(whitespace, 'utf8')
contents = removeLiquidStatements(contents, versionToDeprecate, nextOldestVersion)
const $ = cheerio.load(contents)
expect($('.example1').text()).toBe('\n Alpha\n')
@@ -167,8 +167,8 @@ describe('whitespace', () => {
expect($('.example4').text()).toBe('\n Alpha\n')
})
- test('does not add newlines when no newlines are present', () => {
- let contents = fs.readFileSync(whitespace, 'utf8')
+ test('does not add newlines when no newlines are present', async () => {
+ let contents = await fs.readFile(whitespace, 'utf8')
contents = removeLiquidStatements(contents, versionToDeprecate, nextOldestVersion)
const $ = cheerio.load(contents)
expect($('.example5').text()).toBe('\n Alpha\n')
diff --git a/tests/content/site-data-references.js b/tests/content/site-data-references.js
index c6f0a019bd..045e33843b 100644
--- a/tests/content/site-data-references.js
+++ b/tests/content/site-data-references.js
@@ -3,10 +3,12 @@ const loadSiteData = require('../../lib/site-data')
const { loadPages } = require('../../lib/pages')
const getDataReferences = require('../../lib/get-liquid-data-references')
const frontmatter = require('@github-docs/frontmatter')
-const fs = require('fs')
+const fs = require('fs').promises
const path = require('path')
describe('data references', () => {
+ jest.setTimeout(60 * 1000)
+
let data, pages
beforeAll(async (done) => {
@@ -33,34 +35,34 @@ describe('data references', () => {
expect(errors.length, JSON.stringify(errors, null, 2)).toBe(0)
})
- test('every data reference found in metadata of English content files is defined and has a value', () => {
+ test('every data reference found in metadata of English content files is defined and has a value', async () => {
let errors = []
expect(pages.length).toBeGreaterThan(0)
- pages.forEach(page => {
+ await Promise.all(pages.map(async page => {
const metadataFile = path.join('content', page.relativePath)
- const fileContents = fs.readFileSync(path.join(__dirname, '../..', metadataFile))
+ const fileContents = await fs.readFile(path.join(__dirname, '../..', metadataFile))
const { data: metadata } = frontmatter(fileContents, { filepath: page.fullPath })
const metadataRefs = getDataReferences(JSON.stringify(metadata))
metadataRefs.forEach(key => {
const value = get(data.en, key)
if (typeof value !== 'string') errors.push({ key, value, metadataFile })
})
- })
+ }))
errors = uniqWith(errors, isEqual) // remove duplicates
expect(errors.length, JSON.stringify(errors, null, 2)).toBe(0)
})
- test('every data reference found in English reusable files is defined and has a value', () => {
+ test('every data reference found in English reusable files is defined and has a value', async () => {
let errors = []
const allReusables = data.en.site.data.reusables
const reusables = Object.values(allReusables)
expect(reusables.length).toBeGreaterThan(0)
- reusables.forEach(reusablesPerFile => {
+ await Promise.all(reusables.map(async reusablesPerFile => {
let reusableFile = path.join(__dirname, '../../data/reusables/', getFilenameByValue(allReusables, reusablesPerFile))
- reusableFile = getFilepath(reusableFile)
+ reusableFile = await getFilepath(reusableFile)
const reusableRefs = getDataReferences(JSON.stringify(reusablesPerFile))
@@ -68,21 +70,21 @@ describe('data references', () => {
const value = get(data.en, key)
if (typeof value !== 'string') errors.push({ key, value, reusableFile })
})
- })
+ }))
errors = uniqWith(errors, isEqual) // remove duplicates
expect(errors.length, JSON.stringify(errors, null, 2)).toBe(0)
})
- test('every data reference found in English variable files is defined and has a value', () => {
+ test('every data reference found in English variable files is defined and has a value', async () => {
let errors = []
const allVariables = data.en.site.data.variables
const variables = Object.values(allVariables)
expect(variables.length).toBeGreaterThan(0)
- variables.forEach(variablesPerFile => {
+ await Promise.all(variables.map(async variablesPerFile => {
let variableFile = path.join(__dirname, '../../data/variables/', getFilenameByValue(allVariables, variablesPerFile))
- variableFile = getFilepath(variableFile)
+ variableFile = await getFilepath(variableFile)
const variableRefs = getDataReferences(JSON.stringify(variablesPerFile))
@@ -90,7 +92,7 @@ describe('data references', () => {
const value = get(data.en, key)
if (typeof value !== 'string') errors.push({ key, value, variableFile })
})
- })
+ }))
errors = uniqWith(errors, isEqual) // remove duplicates
expect(errors.length, JSON.stringify(errors, null, 2)).toBe(0)
@@ -102,10 +104,13 @@ function getFilenameByValue (object, value) {
}
// if path exists, assume it's a directory; otherwise, assume a YML extension
-function getFilepath (filepath) {
- filepath = fs.existsSync(filepath)
- ? filepath + '/'
- : filepath + '.yml'
+async function getFilepath (filepath) {
+ try {
+ await fs.stat(filepath)
+ filepath = filepath + '/'
+ } catch (_) {
+ filepath = filepath + '.yml'
+ }
// we only need the relative path
return filepath.replace(path.join(__dirname, '../../'), '')
diff --git a/tests/content/site-data.js b/tests/content/site-data.js
index e563df27d3..adbdaa7596 100644
--- a/tests/content/site-data.js
+++ b/tests/content/site-data.js
@@ -45,11 +45,11 @@ describe('siteData module (English)', () => {
// TODO: re-enable once Janky flakyness is resolved
test.skip('backfills missing translated site data with English values', async () => {
const newFile = path.join(__dirname, '../../data/newfile.yml')
- fs.writeFileSync(newFile, 'newvalue: bar')
+ await fs.writeFile(newFile, 'newvalue: bar')
const data = await loadSiteData()
expect(get(data, 'en.site.data.newfile.newvalue')).toEqual('bar')
expect(get(data, 'ja.site.data.newfile.newvalue')).toEqual('bar')
- fs.unlinkSync(newFile)
+ await fs.unlink(newFile)
})
test('all Liquid templating is valid', async () => {
diff --git a/tests/fixtures/rest-redirects.json b/tests/fixtures/rest-redirects.json
index 01470158f7..d6b4e852f2 100644
--- a/tests/fixtures/rest-redirects.json
+++ b/tests/fixtures/rest-redirects.json
@@ -219,7 +219,7 @@
"/en/enterprise/2.20/v3/enterprise-admin/orgs": "/en/enterprise-server@2.20/rest/reference/enterprise-admin#orgs",
"/en/enterprise/2.20/v3/enterprise-admin/pre_receive_environments": "/en/enterprise-server@2.20/rest/reference/enterprise-admin#pre-receive-environments",
"/en/enterprise/2.20/v3/enterprise-admin/pre_receive_hooks": "/en/enterprise-server@2.20/rest/reference/enterprise-admin#pre-receive-hooks",
- "/en/enterprise/2.20/v3/enterprise-admin/repo_pre_receive_hooks": "/en/enterprise-server@2.20/rest/reference/enterprise-admin#repo-pre-receive-hooks",
+ "/en/enterprise/2.20/v3/enterprise-admin/repo_pre_receive_hooks": "/en/enterprise-server@2.20/rest/reference/enterprise-admin#repository-pre-receive-hooks",
"/en/enterprise/2.20/v3/enterprise-admin/search_indexing": "/en/enterprise-server@2.20/rest/reference/enterprise-admin#search-indexing",
"/en/enterprise/2.20/v3/enterprise-admin/users": "/en/enterprise-server@2.20/rest/reference/enterprise-admin#users",
"/en/enterprise/2.20/v3/gists/comments": "/en/enterprise-server@2.20/rest/reference/gists#comments",
@@ -243,7 +243,7 @@
"/en/enterprise/2.20/v3/markdown": "/en/enterprise-server@2.20/rest/reference/markdown",
"/en/enterprise/2.20/v3/meta": "/en/enterprise-server@2.20/rest/reference/meta",
"/en/enterprise/2.20/v3/oauth_authorizations": "/en/enterprise-server@2.20/rest/reference/oauth-authorizations",
- "/en/enterprise/2.20/v3/orgs/hooks": "/en/enterprise-server@2.20/rest/reference/orgs#hooks",
+ "/en/enterprise/2.20/v3/orgs/hooks": "/en/enterprise-server@2.20/rest/reference/orgs#webhooks",
"/en/enterprise/2.20/v3/orgs": "/en/enterprise-server@2.20/rest/reference/orgs",
"/en/enterprise/2.20/v3/orgs/members": "/en/enterprise-server@2.20/rest/reference/orgs#members",
"/en/enterprise/2.20/v3/orgs/outside_collaborators": "/en/enterprise-server@2.20/rest/reference/orgs#outside-collaborators",
@@ -267,13 +267,13 @@
"/en/enterprise/2.20/v3/repos/deployments": "/en/enterprise-server@2.20/rest/reference/repos#deployments",
"/en/enterprise/2.20/v3/repos/downloads": "/en/enterprise-server@2.20/rest/reference/repos#downloads",
"/en/enterprise/2.20/v3/repos/forks": "/en/enterprise-server@2.20/rest/reference/repos#forks",
- "/en/enterprise/2.20/v3/repos/hooks": "/en/enterprise-server@2.20/rest/reference/repos#hooks",
+ "/en/enterprise/2.20/v3/repos/hooks": "/en/enterprise-server@2.20/rest/reference/repos#webhooks",
"/en/enterprise/2.20/v3/repos": "/en/enterprise-server@2.20/rest/reference/repos",
"/en/enterprise/2.20/v3/repos/invitations": "/en/enterprise-server@2.20/rest/reference/repos#invitations",
"/en/enterprise/2.20/v3/repos/keys": "/en/enterprise-server@2.20/rest/reference/repos#keys",
"/en/enterprise/2.20/v3/repos/merging": "/en/enterprise-server@2.20/rest/reference/repos#merging",
"/en/enterprise/2.20/v3/repos/pages": "/en/enterprise-server@2.20/rest/reference/repos#pages",
- "/en/enterprise/2.20/v3/repos/pre_receive_hooks": "/en/enterprise-server@2.20/rest/reference/repos#pre-receive-hooks",
+ "/en/enterprise/2.20/v3/repos/pre_receive_hooks": "/en/enterprise-server@2.20/rest/reference/enterprise-admin#repository-pre-receive-hooks",
"/en/enterprise/2.20/v3/repos/releases": "/en/enterprise-server@2.20/rest/reference/repos#releases",
"/en/enterprise/2.20/v3/repos/statistics": "/en/enterprise-server@2.20/rest/reference/repos#statistics",
"/en/enterprise/2.20/v3/repos/statuses": "/en/enterprise-server@2.20/rest/reference/repos#statuses",
@@ -316,7 +316,7 @@
"/en/enterprise/2.21/v3/enterprise-admin/orgs": "/en/enterprise-server@2.21/rest/reference/enterprise-admin#orgs",
"/en/enterprise/2.21/v3/enterprise-admin/pre_receive_environments": "/en/enterprise-server@2.21/rest/reference/enterprise-admin#pre-receive-environments",
"/en/enterprise/2.21/v3/enterprise-admin/pre_receive_hooks": "/en/enterprise-server@2.21/rest/reference/enterprise-admin#pre-receive-hooks",
- "/en/enterprise/2.21/v3/enterprise-admin/repo_pre_receive_hooks": "/en/enterprise-server@2.21/rest/reference/enterprise-admin#repo-pre-receive-hooks",
+ "/en/enterprise/2.21/v3/enterprise-admin/repo_pre_receive_hooks": "/en/enterprise-server@2.21/rest/reference/enterprise-admin#repository-pre-receive-hooks",
"/en/enterprise/2.21/v3/enterprise-admin/search_indexing": "/en/enterprise-server@2.21/rest/reference/enterprise-admin#search-indexing",
"/en/enterprise/2.21/v3/enterprise-admin/users": "/en/enterprise-server@2.21/rest/reference/enterprise-admin#users",
"/en/enterprise/2.21/v3/gists/comments": "/en/enterprise-server@2.21/rest/reference/gists#comments",
@@ -340,7 +340,7 @@
"/en/enterprise/2.21/v3/markdown": "/en/enterprise-server@2.21/rest/reference/markdown",
"/en/enterprise/2.21/v3/meta": "/en/enterprise-server@2.21/rest/reference/meta",
"/en/enterprise/2.21/v3/oauth_authorizations": "/en/enterprise-server@2.21/rest/reference/oauth-authorizations",
- "/en/enterprise/2.21/v3/orgs/hooks": "/en/enterprise-server@2.21/rest/reference/orgs#hooks",
+ "/en/enterprise/2.21/v3/orgs/hooks": "/en/enterprise-server@2.21/rest/reference/orgs#webhooks",
"/en/enterprise/2.21/v3/orgs": "/en/enterprise-server@2.21/rest/reference/orgs",
"/en/enterprise/2.21/v3/orgs/members": "/en/enterprise-server@2.21/rest/reference/orgs#members",
"/en/enterprise/2.21/v3/orgs/outside_collaborators": "/en/enterprise-server@2.21/rest/reference/orgs#outside-collaborators",
@@ -364,13 +364,13 @@
"/en/enterprise/2.21/v3/repos/deployments": "/en/enterprise-server@2.21/rest/reference/repos#deployments",
"/en/enterprise/2.21/v3/repos/downloads": "/en/enterprise-server@2.21/rest/reference/repos#downloads",
"/en/enterprise/2.21/v3/repos/forks": "/en/enterprise-server@2.21/rest/reference/repos#forks",
- "/en/enterprise/2.21/v3/repos/hooks": "/en/enterprise-server@2.21/rest/reference/repos#hooks",
+ "/en/enterprise/2.21/v3/repos/hooks": "/en/enterprise-server@2.21/rest/reference/repos#webhooks",
"/en/enterprise/2.21/v3/repos": "/en/enterprise-server@2.21/rest/reference/repos",
"/en/enterprise/2.21/v3/repos/invitations": "/en/enterprise-server@2.21/rest/reference/repos#invitations",
"/en/enterprise/2.21/v3/repos/keys": "/en/enterprise-server@2.21/rest/reference/repos#keys",
"/en/enterprise/2.21/v3/repos/merging": "/en/enterprise-server@2.21/rest/reference/repos#merging",
"/en/enterprise/2.21/v3/repos/pages": "/en/enterprise-server@2.21/rest/reference/repos#pages",
- "/en/enterprise/2.21/v3/repos/pre_receive_hooks": "/en/enterprise-server@2.21/rest/reference/repos#pre-receive-hooks",
+ "/en/enterprise/2.21/v3/repos/pre_receive_hooks": "/en/enterprise-server@2.21/rest/reference/enterprise-admin#repository-pre-receive-hooks",
"/en/enterprise/2.21/v3/repos/releases": "/en/enterprise-server@2.21/rest/reference/repos#releases",
"/en/enterprise/2.21/v3/repos/statistics": "/en/enterprise-server@2.21/rest/reference/repos#statistics",
"/en/enterprise/2.21/v3/repos/statuses": "/en/enterprise-server@2.21/rest/reference/repos#statuses",
@@ -453,7 +453,7 @@
"/v3/migrations/users": "/en/free-pro-team@latest/rest/reference/migrations#users",
"/v3/oauth_authorizations": "/en/enterprise-server/rest/reference/oauth-authorizations",
"/v3/orgs/blocking": "/en/free-pro-team@latest/rest/reference/orgs#blocking",
- "/v3/orgs/hooks": "/en/free-pro-team@latest/rest/reference/orgs#hooks",
+ "/v3/orgs/hooks": "/en/free-pro-team@latest/rest/reference/orgs#webhooks",
"/v3/orgs": "/en/free-pro-team@latest/rest/reference/orgs",
"/v3/orgs/members": "/en/free-pro-team@latest/rest/reference/orgs#members",
"/v3/orgs/migrations": "/en/free-pro-team@latest/rest/reference/orgs#migrations",
@@ -479,13 +479,13 @@
"/v3/repos/deployments": "/en/free-pro-team@latest/rest/reference/repos#deployments",
"/v3/repos/downloads": "/en/free-pro-team@latest/rest/reference/repos#downloads",
"/v3/repos/forks": "/en/free-pro-team@latest/rest/reference/repos#forks",
- "/v3/repos/hooks": "/en/free-pro-team@latest/rest/reference/repos#hooks",
+ "/v3/repos/hooks": "/en/free-pro-team@latest/rest/reference/repos#webhooks",
"/v3/repos": "/en/free-pro-team@latest/rest/reference/repos",
"/v3/repos/invitations": "/en/free-pro-team@latest/rest/reference/repos#invitations",
"/v3/repos/keys": "/en/free-pro-team@latest/rest/reference/repos#keys",
"/v3/repos/merging": "/en/free-pro-team@latest/rest/reference/repos#merging",
"/v3/repos/pages": "/en/free-pro-team@latest/rest/reference/repos#pages",
- "/v3/repos/pre_receive_hooks": "/en/free-pro-team@latest/rest/reference/repos#pre-receive-hooks",
+ "/v3/repos/pre_receive_hooks": "/en/enterprise-server/rest/reference/enterprise-admin#repository-pre-receive-hooks",
"/v3/repos/releases": "/en/free-pro-team@latest/rest/reference/repos#releases",
"/v3/repos/statistics": "/en/free-pro-team@latest/rest/reference/repos#statistics",
"/v3/repos/statuses": "/en/free-pro-team@latest/rest/reference/repos#statuses",
diff --git a/tests/graphql/build-changelog-test.js b/tests/graphql/build-changelog-test.js
index 1a55d59bab..4620d4aeee 100644
--- a/tests/graphql/build-changelog-test.js
+++ b/tests/graphql/build-changelog-test.js
@@ -1,6 +1,6 @@
const yaml = require('js-yaml')
const { createChangelogEntry, cleanPreviewTitle, previewAnchor, prependDatedEntry } = require('../../script/graphql/build-changelog')
-const fs = require('fs')
+const fs = require('fs').promises
const MockDate = require('mockdate')
const expectedChangelogEntry = require('../fixtures/changelog-entry')
const expectedUpdatedChangelogFile = require('../fixtures/updated-changelog-file')
@@ -111,18 +111,18 @@ describe('updating the changelog file', () => {
MockDate.reset()
})
- it('modifies the entry object and the file on disk', () => {
+ it('modifies the entry object and the file on disk', async () => {
const testTargetPath = 'tests/graphql/example_changelog.json'
- const previousContents = fs.readFileSync(testTargetPath)
+ const previousContents = await fs.readFile(testTargetPath)
const exampleEntry = { someStuff: true }
const expectedDate = '2020-11-20'
MockDate.set(expectedDate)
prependDatedEntry(exampleEntry, testTargetPath)
- const newContents = fs.readFileSync(testTargetPath, 'utf8')
+ const newContents = await fs.readFile(testTargetPath, 'utf8')
// reset the file:
- fs.writeFileSync(testTargetPath, previousContents)
+ await fs.writeFile(testTargetPath, previousContents)
expect(exampleEntry).toEqual({ someStuff: true, date: expectedDate })
expect(JSON.parse(newContents)).toEqual(expectedUpdatedChangelogFile)
diff --git a/tests/meta/orphan-tests.js b/tests/meta/orphan-tests.js
index 770b10389c..9d561140c6 100644
--- a/tests/meta/orphan-tests.js
+++ b/tests/meta/orphan-tests.js
@@ -1,20 +1,28 @@
-const fs = require('fs')
+const fs = require('fs').promises
const path = require('path')
+const { filter: asyncFilter } = require('async')
describe('check for orphan tests', () => {
- test('all tests are in sub-directories', () => {
+ test('all tests are in sub-directories', async () => {
// A known list of exceptions that can live outside of directories
const EXCEPTIONS = ['README.md']
const pathToTests = path.join(process.cwd(), 'tests')
// Get a list of files/directories in `/tests`
- const testDirectory = fs.readdirSync(pathToTests)
+ const testDirectory = await fs.readdir(pathToTests)
- const filteredList = testDirectory
+ let filteredList = testDirectory
// Filter out our exceptions
.filter(item => !EXCEPTIONS.includes(item))
- // Don't include directories
- .filter(item => !fs.statSync(path.join(pathToTests, item)).isDirectory())
+ // Don't include directories
+ filteredList = await asyncFilter(
+ filteredList,
+ async item => !(
+ await fs.stat(
+ path.join(pathToTests, item)
+ )
+ ).isDirectory()
+ )
expect(filteredList).toHaveLength(0)
})
diff --git a/tests/rendering/rest.js b/tests/rendering/rest.js
index d26e1b74ad..34a9428a04 100644
--- a/tests/rendering/rest.js
+++ b/tests/rendering/rest.js
@@ -1,4 +1,4 @@
-const fs = require('fs')
+const fs = require('fs').promises
const path = require('path')
const { difference, isPlainObject } = require('lodash')
const { getJSON } = require('../helpers/supertest')
@@ -17,7 +17,7 @@ describe('REST references docs', () => {
test('markdown file exists for every operationId prefix in the api.github.com schema', async () => {
const { categories } = require('../../lib/rest')
const referenceDir = path.join(__dirname, '../../content/rest/reference')
- const filenames = fs.readdirSync(referenceDir)
+ const filenames = (await fs.readdir(referenceDir))
.filter(filename => !excludeFromResourceNameCheck.find(excludedFile => filename.endsWith(excludedFile)))
.map(filename => filename.replace('.md', ''))
diff --git a/tests/routing/redirects.js b/tests/routing/redirects.js
index 740cd67d55..b41a0888a8 100644
--- a/tests/routing/redirects.js
+++ b/tests/routing/redirects.js
@@ -17,8 +17,8 @@ describe('redirects', () => {
done()
})
- test('page.redirects is an array', () => {
- const page = new Page({
+ test('page.redirects is an array', async () => {
+ const page = await Page.init({
relativePath: 'github/collaborating-with-issues-and-pull-requests/about-branches.md',
basePath: path.join(__dirname, '../../content'),
languageCode: 'en'
@@ -26,8 +26,8 @@ describe('redirects', () => {
expect(isPlainObject(page.redirects)).toBe(true)
})
- test('dotcom homepage page.redirects', () => {
- const page = new Page({
+ test('dotcom homepage page.redirects', async () => {
+ const page = await Page.init({
relativePath: 'github/index.md',
basePath: path.join(__dirname, '../../content'),
languageCode: 'en'
@@ -41,7 +41,7 @@ describe('redirects', () => {
})
test('converts single `redirect_from` strings values into arrays', async () => {
- const page = new Page({
+ const page = await Page.init({
relativePath: 'github/collaborating-with-issues-and-pull-requests/about-conversations-on-github.md',
basePath: path.join(__dirname, '../../content'),
languageCode: 'en'
diff --git a/tests/unit/data-directory/filename-to-key.js b/tests/unit/data-directory/filename-to-key.js
new file mode 100644
index 0000000000..052f78cf98
--- /dev/null
+++ b/tests/unit/data-directory/filename-to-key.js
@@ -0,0 +1,15 @@
+const filenameToKey = require('../../../lib/filename-to-key')
+
+describe('filename-to-key', () => {
+ test('converts filenames to object keys', () => {
+ expect(filenameToKey('foo/bar/baz.txt')).toBe('foo.bar.baz')
+ })
+
+ test('ignores leading slash on filenames', () => {
+ expect(filenameToKey('/foo/bar/baz.txt')).toBe('foo.bar.baz')
+ })
+
+ test('supports MS Windows paths', () => {
+ expect(filenameToKey('path\\to\\file.txt')).toBe('path.to.file')
+ })
+})
diff --git a/tests/unit/data-directory/fixtures/README.md b/tests/unit/data-directory/fixtures/README.md
new file mode 100644
index 0000000000..fb62e0bc60
--- /dev/null
+++ b/tests/unit/data-directory/fixtures/README.md
@@ -0,0 +1 @@
+I am a README. I am ignored by default.
\ No newline at end of file
diff --git a/tests/unit/data-directory/fixtures/bar.yaml b/tests/unit/data-directory/fixtures/bar.yaml
new file mode 100644
index 0000000000..d028f54fed
--- /dev/null
+++ b/tests/unit/data-directory/fixtures/bar.yaml
@@ -0,0 +1 @@
+another_markup_language: 'yes'
diff --git a/tests/unit/data-directory/fixtures/foo.json b/tests/unit/data-directory/fixtures/foo.json
new file mode 100644
index 0000000000..8fd3eb5c42
--- /dev/null
+++ b/tests/unit/data-directory/fixtures/foo.json
@@ -0,0 +1 @@
+{"meaningOfLife": 42}
\ No newline at end of file
diff --git a/tests/unit/data-directory/fixtures/nested/baz.md b/tests/unit/data-directory/fixtures/nested/baz.md
new file mode 100644
index 0000000000..c2be33651c
--- /dev/null
+++ b/tests/unit/data-directory/fixtures/nested/baz.md
@@ -0,0 +1 @@
+I am markdown!
\ No newline at end of file
diff --git a/tests/unit/data-directory/index.js b/tests/unit/data-directory/index.js
new file mode 100644
index 0000000000..0a6bdcf793
--- /dev/null
+++ b/tests/unit/data-directory/index.js
@@ -0,0 +1,40 @@
+const path = require('path')
+const dataDirectory = require('../../../lib/data-directory')
+const fixturesDir = path.join(__dirname, 'fixtures')
+
+describe('data-directory', () => {
+ test('works', async () => {
+ const data = await dataDirectory(fixturesDir)
+ const expected = {
+ bar: { another_markup_language: 'yes' },
+ foo: { meaningOfLife: 42 },
+ nested: { baz: 'I am markdown!' }
+ }
+ expect(data).toEqual(expected)
+ })
+
+ test('option: preprocess function', async () => {
+ const preprocess = function (content) {
+ return content.replace('markdown', 'MARKDOWN')
+ }
+ const data = await dataDirectory(fixturesDir, { preprocess })
+ expect(data.nested.baz).toBe('I am MARKDOWN!')
+ })
+
+ test('option: extensions array', async () => {
+ const extensions = ['.yaml', 'markdown']
+ const data = await dataDirectory(fixturesDir, { extensions })
+ expect('bar' in data).toBe(true)
+ expect('foo' in data).toBe(false) // JSON file should be ignored
+ })
+
+ test('option: ignorePatterns', async () => {
+ const ignorePatterns = []
+
+ // README is ignored by default
+ expect('README' in await dataDirectory(fixturesDir)).toBe(false)
+
+ // README can be included by setting empty ignorePatterns array
+ expect('README' in await dataDirectory(fixturesDir, { ignorePatterns })).toBe(true)
+ })
+})
diff --git a/tests/unit/early-access.js b/tests/unit/early-access.js
index f8ad0b3b88..80aa48373f 100644
--- a/tests/unit/early-access.js
+++ b/tests/unit/early-access.js
@@ -1,4 +1,4 @@
-const fs = require('fs')
+const fs = require('fs').promises
const path = require('path')
const { GITHUB_ACTIONS, GITHUB_REPOSITORY } = process.env
@@ -7,17 +7,17 @@ const testViaActionsOnly = runningActionsOnInternalRepo ? test : test.skip
describe('cloning early-access', () => {
testViaActionsOnly('the content directory exists', async () => {
- const eaContentDir = path.join(process.cwd(), 'content/early-access')
- expect(fs.existsSync(eaContentDir)).toBe(true)
+ const eaDir = path.join(process.cwd(), 'content/early-access')
+ expect(await fs.stat(eaDir)).toBeTruthy()
})
testViaActionsOnly('the data directory exists', async () => {
- const eaContentDir = path.join(process.cwd(), 'data/early-access')
- expect(fs.existsSync(eaContentDir)).toBe(true)
+ const eaDir = path.join(process.cwd(), 'data/early-access')
+ expect(await fs.stat(eaDir)).toBeTruthy()
})
testViaActionsOnly('the assets/images directory exists', async () => {
- const eaContentDir = path.join(process.cwd(), 'assets/images/early-access')
- expect(fs.existsSync(eaContentDir)).toBe(true)
+ const eaDir = path.join(process.cwd(), 'assets/images/early-access')
+ expect(await fs.stat(eaDir)).toBeTruthy()
})
})
diff --git a/tests/unit/find-page.js b/tests/unit/find-page.js
index bb422df1b3..5772db5f01 100644
--- a/tests/unit/find-page.js
+++ b/tests/unit/find-page.js
@@ -8,7 +8,7 @@ describe('find page', () => {
jest.setTimeout(1000 * 1000)
test('falls back to the English page if it can\'t find a localized page', async () => {
- const page = new Page({
+ const page = await Page.init({
relativePath: 'page-that-does-not-exist-in-translations-dir.md',
basePath: path.join(__dirname, '../fixtures'),
languageCode: 'en'
@@ -24,7 +24,7 @@ describe('find page', () => {
})
test('follows redirects', async () => {
- const page = new Page({
+ const page = await Page.init({
relativePath: 'page-with-redirects.md',
basePath: path.join(__dirname, '../fixtures'),
languageCode: 'en'
diff --git a/tests/unit/liquid-helpers.js b/tests/unit/liquid-helpers.js
index ee6b8d3ecf..7a66b64a48 100644
--- a/tests/unit/liquid-helpers.js
+++ b/tests/unit/liquid-helpers.js
@@ -1,10 +1,11 @@
const { liquid } = require('../../lib/render-content')
const { loadPageMap } = require('../../lib/pages')
const entities = new (require('html-entities').XmlEntities)()
-const { set } = require('lodash')
const nonEnterpriseDefaultVersion = require('../../lib/non-enterprise-default-version')
describe('liquid helper tags', () => {
+ jest.setTimeout(60 * 1000)
+
const context = {}
let pageMap
beforeAll(async (done) => {
@@ -13,11 +14,16 @@ describe('liquid helper tags', () => {
context.currentVersion = nonEnterpriseDefaultVersion
context.pages = pageMap
context.redirects = []
- context.site = {}
+ context.site = {
+ data: {
+ reusables: {
+ example: 'a rose by any other name\nwould smell as sweet'
+ }
+ }
+ }
context.page = {
relativePath: 'desktop/index.md'
}
- set(context.site, 'data.reusables.example', 'a rose by any other name\nwould smell as sweet')
done()
})
@@ -81,8 +87,6 @@ describe('liquid helper tags', () => {
})
describe('indented_data_reference tag', () => {
- set(context.site, 'data.reusables.example', 'a rose by any other name\nwould smell as sweet')
-
test('without any number of spaces specified', async () => {
const template = '{% indented_data_reference site.data.reusables.example %}'
const expected = ` a rose by any other name
@@ -115,4 +119,47 @@ would smell as sweet`
expect(output).toBe(expected)
})
})
+
+ describe('data tag', () => {
+ test(
+ 'handles bracketed array access within for-in loop',
+ async () => {
+ const template = `
+{% for term in site.data.glossaries.external %}
+### {% data glossaries.external[forloop.index0].term %}
+{% data glossaries.external[forloop.index0].description %}
+---
+{% endfor %}`
+
+ const localContext = { ...context }
+ localContext.site = {
+ data: {
+ variables: {
+ fire_emoji: ':fire:'
+ },
+ glossaries: {
+ external: [
+ { term: 'lit', description: 'Awesome things. {% data variables.fire_emoji %}' },
+ { term: 'Zhu Li', description: '_"Zhu Li, do the thing!"_ :point_up:' }
+ ]
+ }
+ }
+ }
+
+ const expected = `
+
+### lit
+Awesome things. :fire:
+---
+
+### Zhu Li
+_"Zhu Li, do the thing!"_ :point_up:
+---
+`
+
+ const output = await liquid.parseAndRender(template, localContext)
+ expect(output).toBe(expected)
+ }
+ )
+ })
})
diff --git a/tests/unit/page.js b/tests/unit/page.js
index 5d1b46a0c3..44f98c94a5 100644
--- a/tests/unit/page.js
+++ b/tests/unit/page.js
@@ -15,14 +15,14 @@ const opts = {
}
describe('Page class', () => {
- test('preserves file path info', () => {
- const page = new Page(opts)
+ test('preserves file path info', async () => {
+ const page = await Page.init(opts)
expect(page.relativePath).toBe('github/collaborating-with-issues-and-pull-requests/about-branches.md')
expect(page.fullPath.includes(page.relativePath)).toBe(true)
})
- test('does not error out on translated TOC with no links', () => {
- const page = new Page({
+ test('does not error out on translated TOC with no links', async () => {
+ const page = await Page.init({
relativePath: 'translated-toc-with-no-links-index.md',
basePath: path.join(__dirname, '../fixtures'),
languageCode: 'ja'
@@ -31,30 +31,34 @@ describe('Page class', () => {
})
describe('showMiniToc page property', () => {
- const article = new Page({
- relativePath: 'sample-article.md',
- basePath: path.join(__dirname, '../fixtures'),
- languageCode: 'en'
- })
+ let article, articleWithFM, tocPage, mapTopic
- const articleWithFM = new Page({
- showMiniToc: false,
- relativePath: article.relativePath,
- basePath: article.basePath,
- languageCode: article.languageCode
- })
+ beforeAll(async () => {
+ article = await Page.init({
+ relativePath: 'sample-article.md',
+ basePath: path.join(__dirname, '../fixtures'),
+ languageCode: 'en'
+ })
- const tocPage = new Page({
- relativePath: 'sample-toc-index.md',
- basePath: path.join(__dirname, '../fixtures'),
- languageCode: 'en'
- })
+ articleWithFM = await Page.init({
+ showMiniToc: false,
+ relativePath: article.relativePath,
+ basePath: article.basePath,
+ languageCode: article.languageCode
+ })
- const mapTopic = new Page({
- mapTopic: true,
- relativePath: article.relativePath,
- basePath: article.basePath,
- languageCode: article.languageCode
+ tocPage = await Page.init({
+ relativePath: 'sample-toc-index.md',
+ basePath: path.join(__dirname, '../fixtures'),
+ languageCode: 'en'
+ })
+
+ mapTopic = await Page.init({
+ mapTopic: true,
+ relativePath: article.relativePath,
+ basePath: article.basePath,
+ languageCode: article.languageCode
+ })
})
test('is true by default on articles', () => {
@@ -76,7 +80,7 @@ describe('Page class', () => {
describe('page.render(context)', () => {
test('rewrites links to include the current language prefix and version', async () => {
- const page = new Page(opts)
+ const page = await Page.init(opts)
const context = {
page: { version: nonEnterpriseDefaultVersion },
currentVersion: nonEnterpriseDefaultVersion,
@@ -99,7 +103,7 @@ describe('Page class', () => {
})
test('rewrites links in the intro to include the current language prefix and version', async () => {
- const page = new Page(opts)
+ const page = await Page.init(opts)
page.rawIntro = '[Pull requests](/articles/about-pull-requests)'
const context = {
page: { version: nonEnterpriseDefaultVersion },
@@ -114,7 +118,7 @@ describe('Page class', () => {
})
test('does not rewrite links that include deprecated enterprise release numbers', async () => {
- const page = new Page({
+ const page = await Page.init({
relativePath: 'admin/enterprise-management/migrating-from-github-enterprise-1110x-to-2123.md',
basePath: path.join(__dirname, '../../content'),
languageCode: 'en'
@@ -133,7 +137,7 @@ describe('Page class', () => {
})
test('does not rewrite links to external redirects', async () => {
- const page = new Page(opts)
+ const page = await Page.init(opts)
page.markdown = `${page.markdown}\n\nSee [Capistrano](/capistrano).`
const context = {
page: { version: nonEnterpriseDefaultVersion },
@@ -150,7 +154,7 @@ describe('Page class', () => {
// But they don't have access to our currently supported versions, which we're testing here.
// This test ensures that this works as expected: {% if enterpriseServerVersions contains currentVersion %}
test('renders the expected Enterprise Server versioned content', async () => {
- const page = new Page({
+ const page = await Page.init({
relativePath: 'page-versioned-for-all-enterprise-releases.md',
basePath: path.join(__dirname, '../fixtures'),
languageCode: 'en'
@@ -184,27 +188,27 @@ describe('Page class', () => {
})
})
- test('preserves `languageCode`', () => {
- const page = new Page(opts)
+ test('preserves `languageCode`', async () => {
+ const page = await Page.init(opts)
expect(page.languageCode).toBe('en')
})
- test('parentProductId getter', () => {
- let page = new Page({
+ test('parentProductId getter', async () => {
+ let page = await Page.init({
relativePath: 'github/some-category/some-article.md',
basePath: path.join(__dirname, '../fixtures/products'),
languageCode: 'en'
})
expect(page.parentProductId).toBe('github')
- page = new Page({
+ page = await Page.init({
relativePath: 'actions/some-category/some-article.md',
basePath: path.join(__dirname, '../fixtures/products'),
languageCode: 'en'
})
expect(page.parentProductId).toBe('actions')
- page = new Page({
+ page = await Page.init({
relativePath: 'admin/some-category/some-article.md',
basePath: path.join(__dirname, '../fixtures/products'),
languageCode: 'en'
@@ -213,26 +217,26 @@ describe('Page class', () => {
})
describe('permalinks', () => {
- test('is an array', () => {
- const page = new Page(opts)
+ test('is an array', async () => {
+ const page = await Page.init(opts)
expect(Array.isArray(page.permalinks)).toBe(true)
})
- test('has a key for every supported enterprise version (and no deprecated versions)', () => {
- const page = new Page(opts)
+ test('has a key for every supported enterprise version (and no deprecated versions)', async () => {
+ const page = await Page.init(opts)
const pageVersions = page.permalinks.map(permalink => permalink.pageVersion)
expect(enterpriseServerReleases.supported.every(version => pageVersions.includes(`enterprise-server@${version}`))).toBe(true)
expect(enterpriseServerReleases.deprecated.every(version => !pageVersions.includes(`enterprise-server@${version}`))).toBe(true)
})
- test('sets versioned values', () => {
- const page = new Page(opts)
+ test('sets versioned values', async () => {
+ const page = await Page.init(opts)
expect(page.permalinks.find(permalink => permalink.pageVersion === nonEnterpriseDefaultVersion).href).toBe(`/en/${nonEnterpriseDefaultVersion}/github/collaborating-with-issues-and-pull-requests/about-branches`)
expect(page.permalinks.find(permalink => permalink.pageVersion === `enterprise-server@${enterpriseServerReleases.oldestSupported}`).href).toBe(`/en/enterprise-server@${enterpriseServerReleases.oldestSupported}/github/collaborating-with-issues-and-pull-requests/about-branches`)
})
- test('homepage permalinks', () => {
- const page = new Page({
+ test('homepage permalinks', async () => {
+ const page = await Page.init({
relativePath: 'index.md',
basePath: path.join(__dirname, '../../content'),
languageCode: 'en'
@@ -242,8 +246,8 @@ describe('Page class', () => {
expect(page.permalinks.find(permalink => permalink.pageVersion === 'homepage').href).toBe('/en')
})
- test('permalinks for dotcom-only pages', () => {
- const page = new Page({
+ test('permalinks for dotcom-only pages', async () => {
+ const page = await Page.init({
relativePath: 'github/getting-started-with-github/signing-up-for-a-new-github-account.md',
basePath: path.join(__dirname, '../../content'),
languageCode: 'en'
@@ -252,8 +256,8 @@ describe('Page class', () => {
expect(page.permalinks.length).toBe(1)
})
- test('permalinks for enterprise-only pages', () => {
- const page = new Page({
+ test('permalinks for enterprise-only pages', async () => {
+ const page = await Page.init({
relativePath: 'products/admin/some-category/some-article.md',
basePath: path.join(__dirname, '../fixtures'),
languageCode: 'en'
@@ -264,8 +268,8 @@ describe('Page class', () => {
expect(pageVersions.includes(nonEnterpriseDefaultVersion)).toBe(false)
})
- test('permalinks for non-GitHub.com products without Enterprise versions', () => {
- const page = new Page({
+ test('permalinks for non-GitHub.com products without Enterprise versions', async () => {
+ const page = await Page.init({
relativePath: 'products/actions/some-category/some-article.md',
basePath: path.join(__dirname, '../fixtures'),
languageCode: 'en'
@@ -274,8 +278,8 @@ describe('Page class', () => {
expect(page.permalinks.length).toBe(1)
})
- test('permalinks for non-GitHub.com products with Enterprise versions', () => {
- const page = new Page({
+ test('permalinks for non-GitHub.com products with Enterprise versions', async () => {
+ const page = await Page.init({
relativePath: '/insights/installing-and-configuring-github-insights/about-github-insights.md',
basePath: path.join(__dirname, '../../content'),
languageCode: 'en'
@@ -318,7 +322,7 @@ describe('Page class', () => {
})
test('fixes translated frontmatter that includes verdadero', async () => {
- const page = new Page({
+ const page = await Page.init({
relativePath: 'article-with-mislocalized-frontmatter.md',
basePath: path.join(__dirname, '../fixtures'),
languageCode: 'ja'
@@ -333,7 +337,7 @@ describe('Page class', () => {
// Note this test will go out of date when we deprecate 2.20
test('pages that apply to newer enterprise versions', async () => {
- const page = new Page({
+ const page = await Page.init({
relativePath: 'github/administering-a-repository/comparing-releases.md',
basePath: path.join(__dirname, '../../content'),
languageCode: 'en'
@@ -343,7 +347,7 @@ describe('Page class', () => {
})
test('index page', async () => {
- const page = new Page({
+ const page = await Page.init({
relativePath: 'index.md',
basePath: path.join(__dirname, '../../content'),
languageCode: 'en'
@@ -352,7 +356,7 @@ describe('Page class', () => {
})
test('enterprise admin index page', async () => {
- const page = new Page({
+ const page = await Page.init({
relativePath: 'admin/index.md',
basePath: path.join(__dirname, '../../content'),
languageCode: 'en'
@@ -366,50 +370,50 @@ describe('Page class', () => {
describe('catches errors thrown in Page class', () => {
test('frontmatter parsing error', () => {
- function getPage () {
- return new Page({
+ async function getPage () {
+ return await Page.init({
relativePath: 'page-with-frontmatter-error.md',
basePath: path.join(__dirname, '../fixtures'),
languageCode: 'en'
})
}
- expect(getPage).toThrowError('invalid frontmatter entry')
+ expect(getPage).rejects.toThrowError('invalid frontmatter entry')
})
test('missing versions frontmatter', () => {
- function getPage () {
- return new Page({
+ async function getPage () {
+ return await Page.init({
relativePath: 'page-with-missing-product-versions.md',
basePath: path.join(__dirname, '../fixtures'),
languageCode: 'en'
})
}
- expect(getPage).toThrowError('versions')
+ expect(getPage).rejects.toThrowError('versions')
})
test('English page with a version in frontmatter that its parent product is not available in', () => {
- function getPage () {
- return new Page({
+ async function getPage () {
+ return await Page.init({
relativePath: 'admin/some-category/some-article-with-mismatched-versions-frontmatter.md',
basePath: path.join(__dirname, '../fixtures/products'),
languageCode: 'en'
})
}
- expect(getPage).toThrowError(/`versions` frontmatter.*? product is not available in/)
+ expect(getPage).rejects.toThrowError(/`versions` frontmatter.*? product is not available in/)
})
test('non-English page with a version in frontmatter that its parent product is not available in', () => {
- function getPage () {
- return new Page({
+ async function getPage () {
+ return await Page.init({
relativePath: 'admin/some-category/some-article-with-mismatched-versions-frontmatter.md',
basePath: path.join(__dirname, '../fixtures/products'),
languageCode: 'es'
})
}
- expect(getPage).toThrowError(/`versions` frontmatter.*? product is not available in/)
+ expect(getPage).rejects.toThrowError(/`versions` frontmatter.*? product is not available in/)
})
})
diff --git a/tests/unit/pages.js b/tests/unit/pages.js
index f27b73ff1b..bb405b5e9c 100644
--- a/tests/unit/pages.js
+++ b/tests/unit/pages.js
@@ -10,6 +10,8 @@ const entities = new Entities()
const { chain, difference } = require('lodash')
describe('pages module', () => {
+ jest.setTimeout(60 * 1000)
+
let pages
beforeAll(async (done) => {