Skip to content

Commit 7349298

Browse files
committed
Merge remote-tracking branch 'private/main'
2 parents a776b84 + 32a8d91 commit 7349298

File tree

16 files changed

+534
-129
lines changed

16 files changed

+534
-129
lines changed

.github/workflows/sync-audit-logs.yml

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,34 +51,38 @@ jobs:
5151
# Needed for gh
5252
GITHUB_TOKEN: ${{ secrets.DOCS_BOT_PAT_BASE }}
5353
run: |
54-
# If nothing to commit, exit now. It's fine. No orphans.
55-
changes=$(git diff --name-only | wc -l)
56-
untracked=$(git status --untracked-files --short | wc -l)
57-
filesChanged=$(git diff --name-only)
58-
# There will always be at least one file changed:
59-
# src/audit-logs/lib/config.json
60-
# If the config file is the only file changed, exit.
61-
if [[ $changes -eq 1 ]] && [[ $untracked -eq 1 ]] && [[ $filesChanged == *lib/config.json ]]; then
62-
echo "There are no changes to commit or untracked files. Exiting..."
54+
echo "Creating a new branch if needed..."
55+
branchname=audit-logs-schema-update-${{ steps.audit-log-allowlists.outputs.COMMIT_SHA }}
56+
remotesha=$(git ls-remote --heads origin $branchname)
57+
if [ -n "$remotesha" ]; then
58+
# output is not empty, it means the remote branch exists
59+
echo "Branch $branchname already exists in 'github/docs-internal'. Exiting..."
6360
exit 0
6461
fi
62+
git checkout -b $branchname
63+
echo "Created a new branch $branchname"
6564
65+
echo "Preparing commit..."
6666
git config --global user.name "docs-bot"
6767
git config --global user.email "77750099+docs-bot@users.noreply.github.com"
68+
git add -A .
69+
echo "Prepared commit"
6870
69-
branchname=audit-logs-schema-update-${{ steps.audit-log-allowlists.outputs.COMMIT_SHA }}
70-
71-
remotesha=$(git ls-remote --heads origin $branchname)
72-
if [ -n "$remotesha" ]; then
73-
# output is not empty, it means the remote branch exists
74-
echo "Branch $branchname already exists in 'github/docs-internal'. Exiting..."
71+
echo "Check if there are changes..."
72+
if git diff-index --cached --quiet HEAD -- . ':(exclude)src/audit-logs/lib/config.json'
73+
then
74+
echo "No real changes (only the SHA in config.json moved). Exiting…"
7575
exit 0
7676
fi
77+
echo "Changes detected, proceeding"
7778
78-
git checkout -b $branchname
79-
git add .
79+
echo "Creating commit..."
8080
git commit -m "Add updated audit log event data"
81+
echo "Created commit"
82+
83+
echo "Pushing commit..."
8184
git push origin $branchname
85+
echo "Pushed commit"
8286
8387
echo "Creating pull request..."
8488
gh pr create \
@@ -89,16 +93,21 @@ jobs:
8993
--repo github/docs-internal \
9094
--label audit-log-pipeline \
9195
--head=$branchname
96+
echo "Created pull request"
9297
9398
# can't approve your own PR, approve with Actions
99+
echo "Approving pull request..."
94100
unset GITHUB_TOKEN
95101
gh auth login --with-token <<< "${{ secrets.GITHUB_TOKEN }}"
96102
gh pr review --approve
103+
echo "Approved pull request"
97104
98105
# Actions can't merge the PR so back to docs-bot to merge the PR
106+
echo "Setting pull request to auto merge..."
99107
unset GITHUB_TOKEN
100108
gh auth login --with-token <<< "${{ secrets.DOCS_BOT_PAT_BASE }}"
101109
gh pr merge --auto
110+
echo "Set pull request to auto merge"
102111
103112
- uses: ./.github/actions/slack-alert
104113
if: ${{ failure() && github.event_name != 'workflow_dispatch' }}

.github/workflows/sync-openapi.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ jobs:
4949
repository: github/models-gateway
5050
path: models-gateway
5151
ref: main
52+
token: ${{ secrets.DOCS_BOT_PAT_BASE }}
5253

5354
- uses: ./.github/actions/node-npm-setup
5455

data/reusables/contributing/content-linter-rules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,4 @@
6565
| GHD041 | third-party-action-pinning | Code examples that use third-party actions must always pin to a full length commit SHA | error | feature, actions |
6666
| GHD042 | liquid-tag-whitespace | Liquid tags should start and end with one whitespace. Liquid tag arguments should be separated by only one whitespace. | error | liquid, format |
6767
| GHD043 | link-quotation | Internal link titles must not be surrounded by quotations | error | links, url |
68-
| GHD022 | liquid-ifversion-versions | Liquid `ifversion`, `elsif`, and `else` tags should be valid and not contain unsupported versions. | error | liquid, versioning |
68+
| GHD044 | octicon-aria-labels | Octicons should always have an aria-label attribute even if aria-hidden. | warning | accessibility, octicons |

src/content-linter/lib/linting-rules/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { tableLiquidVersioning } from './table-liquid-versioning.js'
3333
import { thirdPartyActionPinning } from './third-party-action-pinning.js'
3434
import { liquidTagWhitespace } from './liquid-tag-whitespace.js'
3535
import { linkQuotation } from './link-quotation.js'
36+
import { octiconAriaLabels } from './octicon-aria-labels.js'
3637
import { liquidIfversionVersions } from './liquid-ifversion-versions.js'
3738

3839
const noDefaultAltText = markdownlintGitHub.find((elem) =>
@@ -82,6 +83,6 @@ export const gitHubDocsMarkdownlint = {
8283
thirdPartyActionPinning,
8384
liquidTagWhitespace,
8485
linkQuotation,
85-
liquidIfversionVersions,
86+
octiconAriaLabels,
8687
],
8788
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { TokenKind } from 'liquidjs'
2+
import { getLiquidTokens, getPositionData } from '../helpers/liquid-utils.js'
3+
import { addFixErrorDetail } from '../helpers/utils.js'
4+
/*
5+
Octicons should always have an aria-label attribute even if aria hidden. For example:
6+
7+
DO use aria-label
8+
{% octicon "alert" aria-label="alert" %}
9+
{% octicon "alert" aria-label="alert" aria-hidden="true" %}
10+
{% octicon "alert" aria-label="alert" aria-hidden="true" class="foo" %}
11+
12+
This is necessary for copilot to be able to recognize the svgs correctly when using our API.
13+
14+
*/
15+
16+
export const octiconAriaLabels = {
17+
names: ['GHD044', 'octicon-aria-labels'],
18+
description: 'Octicons should always have an aria-label attribute even if aria-hidden.',
19+
tags: ['accessibility', 'octicons'],
20+
parser: 'markdownit',
21+
function: (params, onError) => {
22+
const content = params.lines.join('\n')
23+
const tokens = getLiquidTokens(content)
24+
.filter((token) => token.kind === TokenKind.Tag)
25+
.filter((token) => token.name === 'octicon')
26+
27+
for (const token of tokens) {
28+
const { lineNumber, column, length } = getPositionData(token, params.lines)
29+
30+
const hasAriaLabel = token.args.includes('aria-label=')
31+
32+
if (!hasAriaLabel) {
33+
const range = [column, length]
34+
35+
const octiconNameMatch = token.args.match(/["']([^"']+)["']/)
36+
const octiconName = octiconNameMatch ? octiconNameMatch[1] : 'icon'
37+
const originalContent = token.content
38+
const fixedContent = originalContent + ` aria-label="${octiconName}"`
39+
40+
addFixErrorDetail(
41+
onError,
42+
lineNumber,
43+
`octicon should have an aria-label even if aria hidden. Try using 'aria-label=${octiconName}'`,
44+
token.content,
45+
range,
46+
{
47+
lineNumber,
48+
editColumn: column,
49+
deleteCount: length,
50+
insertText: `{% ${fixedContent} %}`,
51+
},
52+
)
53+
}
54+
}
55+
},
56+
}

src/content-linter/style/github-docs.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ const githubDocsConfig = {
168168
'partial-markdown-files': true,
169169
'yml-files': true,
170170
},
171+
'octicon-aria-labels': {
172+
// GHD044
173+
severity: 'warning',
174+
'partial-markdown-files': true,
175+
'yml-files': true,
176+
},
171177
}
172178

173179
export const githubDocsFrontmatterConfig = {
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { octiconAriaLabels } from '../../lib/linting-rules/octicon-aria-labels.js'
3+
4+
describe('octicon-aria-labels', () => {
5+
const rule = octiconAriaLabels
6+
7+
test('detects octicon without aria-label', () => {
8+
const errors = []
9+
const onError = (errorInfo) => {
10+
errors.push(errorInfo)
11+
}
12+
13+
const content = ['This is a test with an octicon:', '{% octicon "alert" %}', 'Some more text.']
14+
15+
rule.function({ lines: content }, onError)
16+
17+
expect(errors.length).toBe(1)
18+
expect(errors[0].lineNumber).toBe(2)
19+
expect(errors[0].detail).toContain('aria-label=alert')
20+
expect(errors[0].fixInfo.insertText).toContain('aria-label="alert"')
21+
})
22+
23+
test('ignores octicons with aria-label', () => {
24+
const errors = []
25+
const onError = (errorInfo) => {
26+
errors.push(errorInfo)
27+
}
28+
29+
const content = [
30+
'This is a test with a proper octicon:',
31+
'{% octicon "alert" aria-label="alert" %}',
32+
'Some more text.',
33+
]
34+
35+
rule.function({ lines: content }, onError)
36+
37+
expect(errors.length).toBe(0)
38+
})
39+
40+
test('detects multiple octicons without aria-label', () => {
41+
const errors = []
42+
const onError = (errorInfo) => {
43+
errors.push(errorInfo)
44+
}
45+
46+
const content = [
47+
'This is a test with multiple octicons:',
48+
'{% octicon "alert" %}',
49+
'Some text in between.',
50+
'{% octicon "check" %}',
51+
'More text.',
52+
]
53+
54+
rule.function({ lines: content }, onError)
55+
56+
expect(errors.length).toBe(2)
57+
expect(errors[0].lineNumber).toBe(2)
58+
expect(errors[0].detail).toContain('aria-label=alert')
59+
expect(errors[1].lineNumber).toBe(4)
60+
expect(errors[1].detail).toContain('aria-label=check')
61+
})
62+
63+
test('ignores non-octicon liquid tags', () => {
64+
const errors = []
65+
const onError = (errorInfo) => {
66+
errors.push(errorInfo)
67+
}
68+
69+
const content = [
70+
'This is a test with non-octicon tags:',
71+
'{% data foo.bar %}',
72+
'{% ifversion fpt %}',
73+
'Some text.',
74+
'{% endif %}',
75+
]
76+
77+
rule.function({ lines: content }, onError)
78+
79+
expect(errors.length).toBe(0)
80+
})
81+
82+
test('suggests correct fix for octicon with other attributes', () => {
83+
const errors = []
84+
const onError = (errorInfo) => {
85+
errors.push(errorInfo)
86+
}
87+
88+
const content = [
89+
'This is a test with an octicon with other attributes:',
90+
'{% octicon "plus" aria-hidden="true" class="foo" %}',
91+
'Some more text.',
92+
]
93+
94+
rule.function({ lines: content }, onError)
95+
96+
expect(errors.length).toBe(1)
97+
expect(errors[0].lineNumber).toBe(2)
98+
expect(errors[0].fixInfo.insertText).toContain('aria-label="plus"')
99+
expect(errors[0].fixInfo.insertText).toContain('aria-hidden="true"')
100+
expect(errors[0].fixInfo.insertText).toContain('class="foo"')
101+
})
102+
103+
test('handles octicons with unusual spacing', () => {
104+
const errors = []
105+
const onError = (errorInfo) => {
106+
errors.push(errorInfo)
107+
}
108+
109+
const content = [
110+
'This is a test with unusual spacing:',
111+
'{% octicon "x" %}',
112+
'Some more text.',
113+
]
114+
115+
rule.function({ lines: content }, onError)
116+
117+
expect(errors.length).toBe(1)
118+
expect(errors[0].lineNumber).toBe(2)
119+
expect(errors[0].detail).toContain('aria-label=x')
120+
})
121+
122+
test('handles octicons split across multiple lines', () => {
123+
const errors = []
124+
const onError = (errorInfo) => {
125+
errors.push(errorInfo)
126+
}
127+
128+
const content = [
129+
'This is a test with a multi-line octicon:',
130+
'{% octicon "chevron-down"',
131+
' class="dropdown-menu-icon"',
132+
'%}',
133+
'Some more text.',
134+
]
135+
136+
rule.function({ lines: content }, onError)
137+
138+
expect(errors.length).toBe(1)
139+
expect(errors[0].detail).toContain('aria-label=chevron-down')
140+
})
141+
142+
test('falls back to "icon" when octicon name cannot be determined', () => {
143+
const errors = []
144+
const onError = (errorInfo) => {
145+
errors.push(errorInfo)
146+
}
147+
148+
const content = [
149+
'This is a test with a malformed octicon:',
150+
'{% octicon variable %}',
151+
'Some more text.',
152+
]
153+
154+
rule.function({ lines: content }, onError)
155+
156+
expect(errors.length).toBe(1)
157+
expect(errors[0].detail).toContain('aria-label=icon')
158+
expect(errors[0].fixInfo.insertText).toContain('aria-label="icon"')
159+
})
160+
})

src/frame/lib/page.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import { getAlertTitles } from '#src/languages/lib/get-alert-titles.ts'
88
import getTocItems from './get-toc-items.js'
99
import Permalink from './permalink.js'
1010
import { renderContent } from '#src/content-render/index.js'
11-
import processLearningTracks from '#src/learning-track/lib/process-learning-tracks.js'
11+
import processLearningTracks from '#src/learning-track/lib/process-learning-tracks'
1212
import { productMap } from '#src/products/lib/all-products.ts'
1313
import slash from 'slash'
1414
import readFileContents from './read-file-contents.js'
15-
import getLinkData from '#src/learning-track/lib/get-link-data.js'
15+
import getLinkData from '#src/learning-track/lib/get-link-data'
1616
import getDocumentType from '#src/events/lib/get-document-type.ts'
1717
import { allTools } from '#src/tools/lib/all-tools.ts'
1818
import { renderContentWithFallback } from '#src/languages/lib/render-with-fallback.js'

src/landings/middleware/featured-links.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { Response, NextFunction } from 'express'
22

33
import type { ExtendedRequest, FeaturedLinkExpanded } from '@/types'
4-
import getLinkData from '@/learning-track/lib/get-link-data.js'
5-
import { renderContent } from '@/content-render/index.js'
4+
import getLinkData from '@/learning-track/lib/get-link-data'
5+
import { renderContent } from '@/content-render/index'
66

77
/**
88
* This is the max. number of featured links, by any category, that we
@@ -73,12 +73,20 @@ export default async function featuredLinks(
7373
if (!(key in req.context.page.featuredLinks))
7474
throw new Error('featureLinks key not found in Page')
7575
const pageFeaturedLink = req.context.page.featuredLinks[key]
76-
req.context.featuredLinks[key] = (await getLinkData(
77-
pageFeaturedLink,
76+
// Handle different types of featuredLinks by converting to string array
77+
const stringLinks = Array.isArray(pageFeaturedLink)
78+
? pageFeaturedLink.map((item) => (typeof item === 'string' ? item : item.href))
79+
: []
80+
81+
const linkData = await getLinkData(
82+
stringLinks,
7883
req.context,
7984
{ title: true, intro: true, fullTitle: true },
8085
MAX_FEATURED_LINKS,
81-
)) as FeaturedLinkExpanded[] // Remove ones `getLinkData` is TS
86+
)
87+
// We need to use a type assertion here because the Page interfaces are incompatible
88+
// between our local types and the global types, but the actual runtime objects are compatible
89+
req.context.featuredLinks[key] = (linkData || []) as unknown as FeaturedLinkExpanded[]
8290
}
8391
}
8492

0 commit comments

Comments
 (0)