Skip to content

Commit 5ecebc9

Browse files
authored
Merge pull request #46715 from github/repo-sync
Repo sync
2 parents f63f0ee + a192f8e commit 5ecebc9

4 files changed

Lines changed: 177 additions & 1 deletion

File tree

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
name: Daily non-employee comment digest
2+
3+
# Include issue/PR conversation comments, not inline review comments.
4+
on:
5+
workflow_dispatch:
6+
schedule:
7+
- cron: '20 16 * * *'
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
notify:
14+
if: github.repository == 'github/docs'
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 15
17+
steps:
18+
- name: Check out repo
19+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
20+
with:
21+
persist-credentials: false
22+
23+
- name: Build daily digest
24+
id: digest
25+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
26+
with:
27+
github-token: ${{ secrets.DOCS_BOT_PAT_BASE }}
28+
script: |
29+
// Octokit request logs include author names, even for expected non-member 404s.
30+
github.log = {
31+
...github.log,
32+
debug: () => {},
33+
info: () => {},
34+
warn: () => {},
35+
error: () => {},
36+
};
37+
// Fixed schedule boundaries avoid gaps or overlaps when scheduled runs start late.
38+
const { data: run } = await github.rest.actions.getWorkflowRun({
39+
...context.repo,
40+
run_id: context.runId,
41+
}).catch((error) => {
42+
throw new Error(`Fetching workflow run failed (HTTP ${error.status || 'unknown'}).`);
43+
});
44+
const runCreatedAt = new Date(run.created_at);
45+
const until = new Date(runCreatedAt.getTime());
46+
until.setUTCHours(16, 20, 0, 0);
47+
if (until.getTime() > runCreatedAt.getTime()) {
48+
until.setUTCDate(until.getUTCDate() - 1);
49+
}
50+
const since = new Date(until.getTime() - 24 * 60 * 60 * 1000);
51+
core.info(`Building digest for ${since.toISOString()} to ${until.toISOString()}.`);
52+
53+
const authors = new Map();
54+
let page = 0;
55+
try {
56+
for await (const { data: comments } of github.paginate.iterator(
57+
github.rest.issues.listCommentsForRepo,
58+
{
59+
owner: 'github',
60+
repo: 'docs',
61+
since: since.toISOString(),
62+
sort: 'created',
63+
direction: 'asc',
64+
per_page: 100,
65+
},
66+
)) {
67+
core.info(`Fetched comments page ${++page} (${comments.length} comments).`);
68+
for (const comment of comments) {
69+
const createdAt = Date.parse(comment.created_at);
70+
if (!(createdAt >= since.getTime() && createdAt < until.getTime())) {
71+
continue;
72+
}
73+
const author = comment.user;
74+
if (!author) {
75+
core.warning('Skipping a comment whose author is unavailable.');
76+
continue;
77+
}
78+
const login = author.login.toLowerCase();
79+
if (author.type === 'Bot' || ['docs-bot', 'copilot'].includes(login)) {
80+
continue;
81+
}
82+
if (!authors.has(login)) {
83+
authors.set(login, { login: author.login, comments: [] });
84+
}
85+
authors.get(login).comments.push({
86+
url: comment.html_url,
87+
conversation: comment.issue_url,
88+
});
89+
}
90+
}
91+
} catch (error) {
92+
throw new Error(`Fetching digest comments failed (HTTP ${error.status || 'unknown'}).`);
93+
}
94+
95+
const nonEmployees = [];
96+
let checked = 0;
97+
for (const author of authors.values()) {
98+
core.info(`Checking employee membership (${++checked}/${authors.size}).`);
99+
try {
100+
await github.rest.teams.getMembershipForUserInOrg({
101+
org: 'github',
102+
team_slug: 'employees',
103+
username: author.login,
104+
});
105+
} catch (error) {
106+
if (error.status !== 404) {
107+
throw new Error(`Employee membership check failed (HTTP ${error.status || 'unknown'}).`);
108+
}
109+
nonEmployees.push(author);
110+
}
111+
}
112+
113+
const totalComments = nonEmployees.reduce((sum, author) => sum + author.comments.length, 0);
114+
core.info(`Found ${totalComments} comments by ${nonEmployees.length} non-employees.`);
115+
if (totalComments === 0) {
116+
return;
117+
}
118+
119+
let message = `Daily non-employee comment digest for github/docs\n` +
120+
`${since.toISOString()} to ${until.toISOString()} (UTC)\n` +
121+
`Non-employee users: ${nonEmployees.length}; comments: ${totalComments}\n` +
122+
'Comment bodies omitted; follow the links to read them on GitHub.';
123+
const shortened = '\n\n[Digest shortened to fit one Slack message; some authors or comment links are omitted.]';
124+
const maximumLength = 35000 - shortened.length;
125+
const lines = nonEmployees.flatMap((author) => [
126+
`\n\n@${author.login} (https://github.com/${author.login})\n` +
127+
`Comments: ${author.comments.length}; conversations: ` +
128+
`${new Set(author.comments.map((comment) => comment.conversation)).size}`,
129+
...author.comments.map((comment) => `\n ${comment.url}`),
130+
]);
131+
for (const line of lines) {
132+
if (message.length + line.length > maximumLength) {
133+
message += shortened;
134+
core.warning('Digest shortened to fit one Slack message.');
135+
break;
136+
}
137+
message += line;
138+
}
139+
for (const author of nonEmployees) {
140+
core.setSecret(author.login);
141+
}
142+
core.setOutput('slack_message', message);
143+
144+
- name: Notify the docs-open-source channel
145+
if: ${{ steps.digest.outputs.slack_message != '' }}
146+
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
147+
with:
148+
method: chat.postMessage
149+
token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
150+
errors: true
151+
# Slack channel ID for docs-open-source.
152+
payload: |
153+
channel: C017M6T23GA
154+
text: ${{ toJSON(steps.digest.outputs.slack_message) }}
155+
mrkdwn: false
156+
link_names: false
157+
unfurl_links: false
158+
unfurl_media: false
159+
160+
- uses: ./.github/actions/create-workflow-failure-issue
161+
id: create-failure-issue
162+
if: ${{ failure() }}
163+
with:
164+
token: ${{ secrets.DOCS_BOT_PAT_BASE }}
165+
166+
- name: Notify Slack about workflow failure
167+
if: ${{ failure() }}
168+
uses: ./.github/actions/slack-alert
169+
with:
170+
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
171+
slack_channel_id: C017M6T23GA
172+
issue_url: ${{ steps.create-failure-issue.outputs.issue_url }}

‎content/code-security/concepts/code-scanning/ai-applications-scanning-for-pull-requests.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Scans run when a pull request is created and after each new commit.
3636

3737
Findings from {% data variables.product.prodname_ai_applications_scanning %} appear as pull request comments, the same way other code scanning findings do. Each finding includes:
3838

39-
* The same "AI" label and thumbs up/down feedback control as findings from AI Scan for pull requests
39+
* "AI app" label and thumbs up/down feedback control
4040
* A dismiss button
4141

4242
As with AI Scan, these findings are advisory. They don't block a pull request from being merged.

‎content/copilot/get-started/whats-changing-with-github-enterprise-and-copilot.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,5 @@ Before your transition takes effect:
7070
For an existing enterprise with {% data variables.copilot.copilot_business_short %} or {% data variables.copilot.copilot_enterprise_short %}, contact your {% data variables.product.github %} or Microsoft account team. They can confirm eligibility, review the expected access and billing impact, and coordinate an available transition date. Contacting your account team does not itself change access or billing or guarantee a transition date.
7171

7272
Transitions take effect on the first day of a month. Confirm the effective date with your account team.
73+
74+
Don't have a dedicated account team? [Contact sales](https://github.com/enterprise/contact).

‎src/workflows/tests/actions-workflows.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ const alertWorkflows = workflows.filter(({ data }) =>
107107
const dailyWorkflows = scheduledWorkflows
108108
// purge-fastly.yml soft-purges daily at 02:20 UTC, outside the standard 16:20 slot.
109109
.filter(({ filename }) => filename !== 'purge-fastly.yml')
110+
// The non-employee digest runs every day, including weekends.
111+
.filter(({ filename }) => filename !== 'notify-non-employee-comments.yml')
110112
.filter(({ data }) =>
111113
data.on.schedule!.find(({ cron }: { cron: string }) => /^20 \d{1,2} /.test(cron)),
112114
)

0 commit comments

Comments
 (0)