Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Use the table of contents icon <img alt="Table of contents icon" src="/contribut

**Start here:** Visit [docs.github.com/en/contributing](https://docs.github.com/en/contributing) for complete contributor onboarding and guidelines.

As noted in our contributor onboarding guidelines, if an issue already exists, please don't start work on it until it has been assigned a `help wanted` label.

For repository-specific setup, read the [README](../README.md) file. The official docs site also provides these helpful resources:

- [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)
Expand Down
84 changes: 84 additions & 0 deletions .github/workflows/close-prs-without-help-wanted-issue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Close PRs for issues without help wanted

# docs-internal mirrors workflows to github/docs, so enforce this rule only in the public repo.
on:
pull_request_target:
types: [opened, edited, reopened, synchronize]

permissions:
contents: read
issues: write
pull-requests: write

jobs:
close-without-help-wanted-issue:
if: >-
${{
github.repository == 'github/docs' &&
github.event.pull_request.state == 'open'
}}
runs-on: ubuntu-latest
steps:
- name: Close the pull request if a linked issue lacks the help wanted label
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { owner, repo } = context.repo
const pull_number = context.payload.pull_request.number

const { repository } = await github.graphql(
`query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
state
closingIssuesReferences(first: 100) {
nodes {
labels(first: 100) {
nodes {
name
}
}
}
}
}
}
}`,
{ owner, repo, number: pull_number },
)

const pullRequest = repository?.pullRequest
if (!pullRequest) {
throw new Error(`Pull request #${pull_number} was not found`)
}
if (pullRequest.state !== 'OPEN') {
console.log(`Pull request #${pull_number} is no longer open`)
return
}

const closingIssues = pullRequest.closingIssuesReferences.nodes
if (!closingIssues.length) {
console.log(`Pull request #${pull_number} has no closing issue references`)
return
}

const issuesWithoutHelpWanted = closingIssues.filter(issue =>
!issue.labels.nodes.some(label => label.name.toLowerCase() === 'help wanted'),
)
if (!issuesWithoutHelpWanted.length) {
console.log('Every linked issue has the help wanted label')
return
}

await github.rest.issues.createComment({
owner,
repo,
issue_number: pull_number,
body: "Please don't raise pull requests against an existing issue that another user has raised until it's been marked with a `help wanted` label. We need to properly assess all issues before we can accept community contributions, and determine whether it's something we need to fix internally, or how many articles are affected and what the scope of the fix will be. For now, we're going to close this pull request.",
})

await github.rest.pulls.update({
owner,
repo,
pull_number,
state: 'closed',
})
136 changes: 133 additions & 3 deletions .github/workflows/confirm-internal-staff-work-in-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,123 @@ jobs:

core.setOutput('did_warn', 'true')

- name: Send Slack notification if a GitHub employee who isn't on the Technical Content team opens an issue in public
if: ${{ steps.membership_check.outputs.did_warn && github.repository == 'github/docs' }}
- name: Resolve Slack user ID from username
id: slack_user
if: ${{ steps.membership_check.outputs.did_warn == 'true' }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
with:
github-token: ${{ github.token }}
script: |
const githubLogin = context.payload.sender.login.toLowerCase();

core.info(`Looking up Slack user for @${githubLogin}...`);
core.setOutput('slack_user_id', '');
core.setOutput('slack_mention', `GitHub user \`${context.payload.sender.login}\``);

const token = process.env.SLACK_BOT_TOKEN;
if (!token) {
throw new Error('SLACK_DOCS_BOT_TOKEN is not configured.');
}

let cursor = '';
let pages = 0;
const matches = [];

do {
core.info(`Fetching Slack users.list page ${pages + 1}...`);
const url = new URL('https://slack.com/api/users.list');
url.searchParams.set('limit', '200');
if (cursor) {
url.searchParams.set('cursor', cursor);
}

const response = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});

if (!response.ok) {
throw new Error(`Slack users.list returned HTTP ${response.status}.`);
}

const result = await response.json();
if (!result.ok) {
throw new Error(`Slack users.list failed: ${result.error || 'unknown error'}.`);
}

if (!Array.isArray(result.members)) {
throw new Error('Slack users.list response did not include a members array.');
}

pages += 1;
matches.push(...result.members.filter((member) =>
!member.deleted &&
!member.is_bot &&
!member.is_app_user &&
typeof member.id === 'string' &&
typeof member.name === 'string' &&
member.name.toLowerCase() === githubLogin
));

cursor = result.response_metadata?.next_cursor || '';
} while (cursor);

core.info(`Checked ${pages} pages of users.list. Found ${matches.length} match(es) for @${githubLogin}.`);
if (matches.length !== 1) {
core.warning(`Expected one Slack username matching @${githubLogin}; found ${matches.length}. No DM will be sent.`);
return;
}

core.info(`Will DM Slack user ${matches[0].id}.`);
core.setOutput('slack_user_id', matches[0].id);
core.setOutput('slack_mention', `<@${matches[0].id}>`);

- name: DM the author in Slack
id: slack_dm
if: ${{ steps.slack_user.outputs.slack_user_id != '' }}
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
errors: true
payload: |
channel: ${{ steps.slack_user.outputs.slack_user_id }}
text: ${{ toJSON(format('You opened {0} publicly in github/docs instead of the private repo. An internal confirmation issue was created.', github.event.issue.html_url || github.event.pull_request.html_url)) }}

- name: Log Slack DM result
if: ${{ !cancelled() && steps.membership_check.outputs.did_warn == 'true' }}
env:
SLACK_USER_ID: ${{ steps.slack_user.outputs.slack_user_id }}
SLACK_LOOKUP_OUTCOME: ${{ steps.slack_user.outcome }}
SLACK_DM_OUTCOME: ${{ steps.slack_dm.outcome }}
SLACK_DM_OK: ${{ steps.slack_dm.outputs.ok }}
SLACK_DM_RESPONSE: ${{ steps.slack_dm.outputs.response }}
SLACK_DM_TS: ${{ steps.slack_dm.outputs.ts }}
run: |
echo "Slack user lookup outcome: ${SLACK_LOOKUP_OUTCOME:-not run}"
if [ -z "$SLACK_USER_ID" ]; then
echo "Slack DM skipped because no Slack user ID was resolved."
elif [ "$SLACK_DM_OK" = "true" ]; then
echo "Slack DM sent successfully (timestamp: ${SLACK_DM_TS:-unavailable})."
else
echo "Slack DM did not return success (step outcome: ${SLACK_DM_OUTCOME:-unknown})."
if [ -n "$SLACK_DM_RESPONSE" ]; then
printf '%s\n' "$SLACK_DM_RESPONSE" | jq -r 'if type == "object" and (.error | type) == "string" then "Slack API error: " + .error else "Slack API response did not include an error field." end'
else
echo "Slack DM API response was unavailable."
fi
fi

- name: Notify the docs-open-source channel
id: slack_channel_notification
if: ${{ !cancelled() && steps.membership_check.outputs.did_warn == 'true' }}
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
env:
SLACK_MESSAGE: <@${{ github.actor }}> opened https://github.com/github/docs/issues/${{ github.event.number || github.event.issue.number }} publicly on the github/docs repo instead of a private repo. They have been notified via a new issue in the private repo to confirm this was intentional.
SLACK_CHANNEL_ID: ${{ secrets.DOCS_OPEN_SOURCE_SLACK_CHANNEL_ID }}
SLACK_MESSAGE: ${{ format('{0} opened {1} publicly in github/docs instead of the private repo. An internal confirmation issue was created.', steps.slack_user.outputs.slack_mention, github.event.issue.html_url || github.event.pull_request.html_url) }}
with:
method: chat.postMessage
token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
Expand All @@ -79,6 +190,25 @@ jobs:
channel: ${{ toJSON(env.SLACK_CHANNEL_ID) }}
text: ${{ toJSON(env.SLACK_MESSAGE) }}

- name: Log Slack channel notification result
if: ${{ !cancelled() && steps.membership_check.outputs.did_warn == 'true' }}
env:
SLACK_CHANNEL_OUTCOME: ${{ steps.slack_channel_notification.outcome }}
SLACK_CHANNEL_OK: ${{ steps.slack_channel_notification.outputs.ok }}
SLACK_CHANNEL_RESPONSE: ${{ steps.slack_channel_notification.outputs.response }}
SLACK_CHANNEL_TS: ${{ steps.slack_channel_notification.outputs.ts }}
run: |
if [ "$SLACK_CHANNEL_OK" = "true" ]; then
echo "Slack channel notification sent successfully (timestamp: ${SLACK_CHANNEL_TS:-unavailable})."
else
echo "Slack channel notification did not return success (step outcome: ${SLACK_CHANNEL_OUTCOME:-unknown})."
if [ -n "$SLACK_CHANNEL_RESPONSE" ]; then
printf '%s\n' "$SLACK_CHANNEL_RESPONSE" | jq -r 'if type == "object" and (.error | type) == "string" then "Slack API error: " + .error else "Slack API response did not include an error field." end'
else
echo "Slack channel API response was unavailable."
fi
fi

- name: Check out repo
if: ${{ failure() && github.event_name != 'pull_request_target' }}
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sync-sdk-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -261,12 +261,12 @@ jobs:

- uses: ./.github/actions/create-workflow-failure-issue
id: create-failure-issue
if: ${{ failure() && github.event_name != 'workflow_dispatch' }}
if: ${{ failure() && github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request' }}
with:
token: ${{ secrets.DOCS_BOT_PAT_BASE }}

- uses: ./.github/actions/slack-alert
if: ${{ failure() && github.event_name != 'workflow_dispatch' }}
if: ${{ failure() && github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request' }}
with:
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
issue_url: ${{ steps.create-failure-issue.outputs.issue_url }}
25 changes: 25 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,29 @@ jobs:
if: ${{ matrix.name == 'content-linter' }}
run: echo "${{ steps.changed_files.outputs.all_modified_files }}" > get_diff_files.txt

- name: Check for automated pipeline changes
if: ${{ matrix.name == 'automated-pipelines' && github.event_name == 'pull_request' }}
id: pipeline_files
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
with:
# Count a rename as a delete plus an add, so moving a file out of these paths still matches.
output_renamed_files_as_deleted_and_added: true
# Generated content counts too, so sync PRs render every page.
files: |
.github/workflows/test.yml
package-lock.json
content/code-security/reference/code-scanning/codeql/codeql-cli-manual/**
content/graphql/**
content/rest/**
src/audit-logs/**
src/automated-pipelines/**
src/codeql-cli/**
src/github-apps/**
src/graphql/**
src/rest/**
src/secret-scanning/**
src/webhooks/**
- uses: ./.github/actions/cache-nextjs
if: ${{ env.START_VITEST_SERVER != 'false' }}

Expand Down Expand Up @@ -172,4 +195,6 @@ jobs:
# RUNNER_DEBUG enables timing and path logs when Actions reruns jobs with debug logging.
RUNNER_DEBUG: ${{ runner.debug }}
VITEST_FLAGS: ${{ matrix.name == 'article-api' && '--no-file-parallelism --maxWorkers=1' || '' }}
# PRs render pinned autogenerated pages unless they change pipeline code or output.
AUTOGENERATED_RENDER_ALL: ${{ matrix.name == 'automated-pipelines' && (github.event_name != 'pull_request' || steps.pipeline_files.outputs.any_modified == 'true') }}
run: npm test -- $VITEST_FLAGS src/${{ matrix.name }}/tests/
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Deleting your personal account
intro: You can delete your personal account on {% data variables.location.product_location %} at any time.
intro: You can delete your personal account on {% data variables.location.product_location %} from your account settings.
redirect_from:
- /articles/deleting-a-user-account
- /articles/deleting-your-user-account
Expand Down Expand Up @@ -46,6 +46,14 @@ Before you delete your personal account, make a copy of all repositories, privat
> [!WARNING]
> Once your personal account has been deleted, {% ifversion fpt or ghec %}{% data variables.product.company_short %}{% elsif ghes %}an enterprise owner{% endif %} cannot restore your content.

{% ifversion fpt or ghec %}

If your account is flagged, suspended, or otherwise restricted, **Delete your account** may be unavailable, disabled, or direct you to {% data variables.contact.github_support %} instead of completing deletion. Contact {% data variables.contact.contact_support_page %} to request access to the normal self-service deletion flow.

{% data variables.contact.github_support %} does not delete your personal account on your behalf. Once access is restored, you must complete the steps below to delete your account.

{% endif %}

{% data reusables.user-settings.access_settings %}
{% data reusables.user-settings.account_settings %}
1. At the bottom of the Account Settings page, under "Delete account", click **Delete your account**. Before you can delete your personal account:
Expand Down
6 changes: 5 additions & 1 deletion content/billing/how-tos/troubleshooting/locked-account.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ category:

You can unlock and access your account by updating your account's payment method and resuming paid status. We do not ask you to pay for the time elapsed in locked mode.

A failed authorization hold when you try to sign up for {% data variables.product.prodname_copilot_short %} or increase a budget can also lock your personal account billing and block {% data variables.product.prodname_actions %} or other free-tier access. This can happen even if you plan to stay on {% data variables.product.prodname_free_user %}, have removed the payment method, or see no active {% data variables.product.prodname_copilot_short %} plan or balance in the "Billing and licensing" section under your account's settings page.

You can downgrade your user account or organization to {% data variables.product.prodname_free_team %} to continue with the same advanced features in public repositories. For more information, see [AUTOTITLE](/billing/how-tos/manage-plan-and-licenses/downgrade-plan).

## Unlocking an account's features due to a declined payment
Expand All @@ -40,8 +42,10 @@ If the new billing information is approved, we will immediately charge you for t

When initiating a subscription or trial, we verify the entered payment method using an authorization hold. If the hold is unsuccessful, the chosen subscription or trial will not activate.

If your account's advanced features are locked due to a failed authorization hold, you'll need to update your billing information to trigger a new authorization.
If you want to continue with the chosen subscription, update your billing information to trigger a new authorization.

{% data reusables.billing.manage-payment-info %}

If the new authorization is successful, the account will automatically unlock and you will be able to proceed with the chosen subscription.

If the "Billing and licensing" section under your account's settings page shows no active subscription, balance, or payment issue to resolve, contact {% data variables.contact.contact_support %} to request a review of the billing lock.
Loading
Loading