From 97dfb498f054e0686142e78d2c79cdf33b7e2aa0 Mon Sep 17 00:00:00 2001 From: Sarah Edwards Date: Mon, 25 Jan 2021 08:52:28 -0800 Subject: [PATCH 1/7] Correct routing logic for self-hosted runners (#17418) Co-authored-by: Martin Lopes --- .../using-self-hosted-runners-in-a-workflow.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/content/actions/hosting-your-own-runners/using-self-hosted-runners-in-a-workflow.md b/content/actions/hosting-your-own-runners/using-self-hosted-runners-in-a-workflow.md index 895402a42c86..9154baa567a8 100644 --- a/content/actions/hosting-your-own-runners/using-self-hosted-runners-in-a-workflow.md +++ b/content/actions/hosting-your-own-runners/using-self-hosted-runners-in-a-workflow.md @@ -64,8 +64,11 @@ These labels operate cumulatively, so a self-hosted runner’s labels must match ### Routing precedence for self-hosted runners -If you use both repository-level and organization-level runners, {% data variables.product.prodname_dotcom %} follows an order of precedence when routing jobs to self-hosted runners: - -1. The job's `runs-on` labels are processed. {% data variables.product.prodname_dotcom %} then attempts to locate a runner that matches the label requirements: -2. The job is sent to a repository-level runner that matches the job labels. If no repository-level runner is available (either busy, offline, or no matching labels): -3. The job is sent to an organization-level runner that matches the job labels. If no organization-level runner is available, the job request fails with an error. +When routing a job to a self-hosted runner, {% data variables.product.prodname_dotcom %} looks for a runner that matches the job's `runs-on` labels: + +1. {% data variables.product.prodname_dotcom %} first searches for a runner at the repository level, then at the organization level{% if currentVersion ver_gt "enterprise-server@2.21" %}, then at the enterprise level{% endif %}. +2. The job is then sent to the first matching runner that is online and idle. + - If all matching online runners are busy, the job will queue at the level with the highest number of matching online runners. + - If all matching runners are offline, the job will queue at the level with the highest number of matching offline runners. + - If there are no matching runners at any level, the job will fail. + - If the job remains queued for more than 24 hours, the job will fail. From 9bc90cd9329be056b5e9bcd0e5f1bb4a0574a752 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen <6842965+vanessayuenn@users.noreply.github.com> Date: Mon, 25 Jan 2021 17:57:32 +0100 Subject: [PATCH 2/7] Learning Track navigation banner (#17440) * add middleware to handle `learn` query param * add exception to query-less cache key * add querystring to learning track guides --- data/ui.yml | 3 ++ includes/article.html | 5 ++- includes/learning-track-nav.html | 18 +++++++++ layouts/product-sublanding.html | 10 ++--- lib/get-link-data.js | 45 +++++++++++++++------- lib/page.js | 1 + middleware/index.js | 1 + middleware/learning-track.js | 40 +++++++++++++++++++ middleware/render-page.js | 12 +++++- tests/rendering/learning-tracks.js | 62 ++++++++++++++++++++++++++++++ 10 files changed, 176 insertions(+), 21 deletions(-) create mode 100644 includes/learning-track-nav.html create mode 100644 middleware/learning-track.js create mode 100644 tests/rendering/learning-tracks.js diff --git a/data/ui.yml b/data/ui.yml index 769025e23f27..2b83989f2feb 100644 --- a/data/ui.yml +++ b/data/ui.yml @@ -160,3 +160,6 @@ product_sublanding: tutorial: Tutorial how_to: How-to guide reference: Reference +learning_track_nav: + prevGuide: Previous Guide + nextGuide: Next Guide diff --git a/includes/article.html b/includes/article.html index 52d1d4104af4..bc884154917e 100644 --- a/includes/article.html +++ b/includes/article.html @@ -65,7 +65,10 @@

+
+ {% if currentLearningTrack and currentLearningTrack.trackName %} + {% include learning-track-nav %} + {% endif %} {% include helpfulness %} {% unless page.hidden %}{% include contribution %}{% endunless %}
diff --git a/includes/learning-track-nav.html b/includes/learning-track-nav.html new file mode 100644 index 000000000000..674fafe85e1e --- /dev/null +++ b/includes/learning-track-nav.html @@ -0,0 +1,18 @@ +
+{% assign track = currentLearningTrack %} + + + {% if track.prevGuide %} + {% data ui.learning_track_nav.prevGuide %} + {{track.prevGuide.title}} + {% endif %} + + + + {% if track.nextGuide %} + {% data ui.learning_track_nav.nextGuide %} + {{track.nextGuide.title}} + {% endif %} + + +
\ No newline at end of file diff --git a/layouts/product-sublanding.html b/layouts/product-sublanding.html index d9af6ab059ee..4f8cf90a8a84 100644 --- a/layouts/product-sublanding.html +++ b/layouts/product-sublanding.html @@ -29,7 +29,7 @@

{{ page.shortTitle }}

{% octicon "star-fill" height="24" class="v-align-middle m-2"%}

{{ featuredTrack.title }}

{{ featuredTrack.description }}
- + {% octicon "arrow-right" height="20" %} {% data ui.product_sublanding.start_path %} @@ -37,7 +37,7 @@

{{ featuredTrack.title }}

{% for guide in featuredTrack.guides %}
  • - +
    {{ forloop.index }} @@ -61,7 +61,7 @@

    {% data ui.product_sublanding.learning_paths %}

    {% for track in page.learningTracks offset:1 %} -
    +
    {% for guide in track.guides %} - +
    {{ forloop.index }}
    diff --git a/lib/get-link-data.js b/lib/get-link-data.js index 46a64d773019..8c47ae895a10 100644 --- a/lib/get-link-data.js +++ b/lib/get-link-data.js @@ -5,28 +5,45 @@ const removeFPTFromPath = require('./remove-fpt-from-path') // rawLinks is an array of paths: [ '/foo' ] // we need to convert it to an array of localized objects: [ { href: '/en/foo', title: 'Foo', intro: 'Description here' } ] -module.exports = async (rawLinks, context) => { +module.exports = async (rawLinks, context, option = { title: true, intro: true }) => { if (!rawLinks) return + if (typeof rawLinks === 'string') { + return await processLink(rawLinks, context, option) + } + const links = [] for (const link of rawLinks) { - const linkPath = link.href || link - const version = context.currentVersion === 'homepage' ? nonEnterpriseDefaultVersion : context.currentVersion - const href = removeFPTFromPath(path.join('/', context.currentLanguage, version, linkPath)) + const linkObj = await processLink(link, context, option) + if (!linkObj) { + continue + } else { + links.push(linkObj) + } + } + + return links +} + +const processLink = async (link, context, option) => { + const linkPath = link.href || link + const version = context.currentVersion === 'homepage' ? nonEnterpriseDefaultVersion : context.currentVersion + const href = removeFPTFromPath(path.join('/', context.currentLanguage, version, linkPath)) + + const linkedPage = findPage(href, context.pages, context.redirects) + if (!linkedPage) return null - const linkedPage = findPage(href, context.pages, context.redirects) - if (!linkedPage) continue + const opts = { textOnly: true, encodeEntities: true } - const opts = { textOnly: true, encodeEntities: true } + const result = { href, page: linkedPage } - links.push({ - href, - title: await linkedPage.renderTitle(context, opts), - intro: await linkedPage.renderProp('intro', context, opts), - page: linkedPage - }) + if (option.title) { + result.title = await linkedPage.renderTitle(context, opts) } - return links + if (option.intro) { + result.intro = await linkedPage.renderProp('intro', context, opts) + } + return result } diff --git a/lib/page.js b/lib/page.js index a7072b7bde7d..ec29665bf2c6 100644 --- a/lib/page.js +++ b/lib/page.js @@ -208,6 +208,7 @@ class Page { const track = context.site.data['learning-tracks'][context.currentProduct][trackName] if (!track) continue learningTracks.push({ + trackName, title: await renderContent(track.title, context, { textOnly: true, encodeEntities: true }), description: await renderContent(track.description, context, { textOnly: true, encodeEntities: true }), guides: await getLinkData(track.guides, context) diff --git a/middleware/index.js b/middleware/index.js index 4eead804ec36..68e61948a212 100644 --- a/middleware/index.js +++ b/middleware/index.js @@ -84,6 +84,7 @@ module.exports = function (app) { app.use(require('./enterprise-server-releases')) app.use(require('./dev-toc')) app.use(require('./featured-links')) + app.use(require('./learning-track')) // *** Rendering, must go last *** app.get('/*', asyncMiddleware(require('./render-page'))) diff --git a/middleware/learning-track.js b/middleware/learning-track.js new file mode 100644 index 000000000000..758912378d6f --- /dev/null +++ b/middleware/learning-track.js @@ -0,0 +1,40 @@ +const { getPathWithoutLanguage, getPathWithoutVersion } = require('../lib/path-utils') +const getLinkData = require('../lib/get-link-data') + +module.exports = async (req, res, next) => { + const noTrack = () => { + req.context.currentLearningTrack = {} + return next() + } + + if (!req.context.page) return next() + + const trackName = req.query.learn + if (!trackName) return noTrack() + + const track = req.context.site.data['learning-tracks'][req.context.currentProduct][trackName] + if (!track) return noTrack() + + const currentLearningTrack = { trackName } + + const guidePath = getPathWithoutLanguage(getPathWithoutVersion(req.path)) + const guideIndex = track.guides.findIndex((path) => path === guidePath) + + if (guideIndex < 0) return noTrack() + + if (guideIndex > 0) { + const prevGuidePath = track.guides[guideIndex - 1] + const { href, title } = await getLinkData(prevGuidePath, req.context, { title: true, intro: false }) + currentLearningTrack.prevGuide = { href, title } + } + + if (guideIndex < track.guides.length - 1) { + const nextGuidePath = track.guides[guideIndex + 1] + const { href, title } = await getLinkData(nextGuidePath, req.context, { title: true, intro: false }) + currentLearningTrack.nextGuide = { href, title } + } + + req.context.currentLearningTrack = currentLearningTrack + + return next() +} diff --git a/middleware/render-page.js b/middleware/render-page.js index c2ef5e8f1be6..a81b549e2c5a 100644 --- a/middleware/render-page.js +++ b/middleware/render-page.js @@ -18,11 +18,21 @@ const pageCache = new RedisAccessor({ allowSetFailures: true }) +// a list of query params that *do* alter the rendered page, and therefore should be cached separately +const cacheableQueries = ['learn'] + module.exports = async function renderPage (req, res, next) { const page = req.context.page // Remove any query string (?...) and/or fragment identifier (#...) - const originalUrl = new URL(req.originalUrl, 'https://docs.github.com').pathname + const { pathname, searchParams } = new URL(req.originalUrl, 'https://docs.github.com') + + for (const queryKey in req.query) { + if (!cacheableQueries.includes(queryKey)) { + searchParams.delete(queryKey) + } + } + const originalUrl = pathname + ([...searchParams].length > 0 ? `?${searchParams}` : '') // Serve from the cache if possible (skip during tests) const isCacheable = !process.env.CI && process.env.NODE_ENV !== 'test' && req.method === 'GET' diff --git a/tests/rendering/learning-tracks.js b/tests/rendering/learning-tracks.js new file mode 100644 index 000000000000..bc9c9682f78b --- /dev/null +++ b/tests/rendering/learning-tracks.js @@ -0,0 +1,62 @@ +const { getDOM } = require('../helpers/supertest') + +jest.setTimeout(3 * 60 * 1000) + +describe('learning tracks', () => { + test('render first track as feature track', async () => { + const $ = await getDOM('/en/actions/guides') + expect($('.feature-track')).toHaveLength(1) + const href = $('.feature-track li a').first().attr('href') + const found = href.match(/.*\?learn=(.*)/i) + expect(found).not.toBeNull() + const trackName = found[1] + + // check all the links contain track name + $('.feature-track li a').each((i, elem) => { + expect($(elem).attr('href')).toEqual(expect.stringContaining(`?learn=${trackName}`)) + }) + }) + + test('render other tracks', async () => { + const $ = await getDOM('/en/actions/guides') + expect($('.learning-track').length).toBeGreaterThanOrEqual(4) + $('.learning-track').each((i, trackElem) => { + const href = $(trackElem).find('.Box-header a').first().attr('href') + const found = href.match(/.*\?learn=(.*)/i) + expect(found).not.toBeNull() + const trackName = found[1] + + // check all the links contain track name + $(trackElem).find('a.Box-row').each((i, elem) => { + expect($(elem).attr('href')).toEqual(expect.stringContaining(`?learn=${trackName}`)) + }) + }) + }) +}) + +describe('navigation banner', () => { + test('render navigation banner when url includes correct learning track name', async () => { + const $ = await getDOM('/en/actions/guides/setting-up-continuous-integration-using-workflow-templates?learn=continuous_integration') + expect($('.learning-track-nav')).toHaveLength(1) + const $navLinks = $('.learning-track-nav a') + expect($navLinks).toHaveLength(2) + $navLinks.each((i, elem) => { + expect($(elem).attr('href')).toEqual(expect.stringContaining('?learn=continuous_integration')) + }) + }) + + test('does not include banner when url does not include `learn` param', async () => { + const $ = await getDOM('/en/actions/guides/setting-up-continuous-integration-using-workflow-templates') + expect($('.learning-track-nav')).toHaveLength(0) + }) + + test('does not include banner when url has incorrect `learn` param', async () => { + const $ = await getDOM('/en/actions/guides/setting-up-continuous-integration-using-workflow-templates?learn=not_real') + expect($('.learning-track-nav')).toHaveLength(0) + }) + + test('does not include banner when url is not part of the learning track', async () => { + const $ = await getDOM('/en/actions/learn-github-actions/introduction-to-github-actions?learn=continuous_integration') + expect($('.learning-track-nav')).toHaveLength(0) + }) +}) From 0a920e474f4f9dbed758d6db771bbc59ac9a838a Mon Sep 17 00:00:00 2001 From: Jenn DeForest <42984983+jenndeforest@users.noreply.github.com> Date: Mon, 25 Jan 2021 09:11:24 -0800 Subject: [PATCH 3/7] Update guidelines-for-legal-requests-of-user-data.md (#17338) Add reimbursement policy https://github.com/github/docs-content/issues/3636 cc: @jessephus Co-authored-by: hubwriter --- .../guidelines-for-legal-requests-of-user-data.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/content/github/site-policy/guidelines-for-legal-requests-of-user-data.md b/content/github/site-policy/guidelines-for-legal-requests-of-user-data.md index d80bbd16cc5c..15918305b335 100644 --- a/content/github/site-policy/guidelines-for-legal-requests-of-user-data.md +++ b/content/github/site-policy/guidelines-for-legal-requests-of-user-data.md @@ -215,6 +215,14 @@ c/o Corporation Service Company 2710 Gateway Oaks Drive, Suite 150N Sacramento, CA 95833-3505 ``` +Under state and federal law, GitHub can seek reimbursement for costs associated with compliance with a valid legal demand, such as a subpoena, court order or search warrant. We only charge to recover some costs, and these reimbursements cover only a portion of the costs we actually incur to comply with legal orders. + +While we do not charge in emergency situations or in other exigent circumstances, we seek reimbursement for all other legal requests in accordance with the following schedule, unless otherwise required by law: + +- Initial search of up to 25 identifiers: Free +- Production of subscriber information/data for up to 5 accounts: Free +- Production of subscriber information/data for more than 5 accounts: $20 per account +- Secondary searches: $10 per search Please make your requests as specific and narrow as possible, including the following information: From 7dd6c93827418edb0f9edecfe379589b0ee380f5 Mon Sep 17 00:00:00 2001 From: Sarah Edwards Date: Mon, 25 Jan 2021 09:15:45 -0800 Subject: [PATCH 4/7] security event permissions for new codescanning endpoints (#17451) --- content/rest/reference/permissions-required-for-github-apps.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/content/rest/reference/permissions-required-for-github-apps.md b/content/rest/reference/permissions-required-for-github-apps.md index 676f2a96b914..e4c3b5ffec64 100644 --- a/content/rest/reference/permissions-required-for-github-apps.md +++ b/content/rest/reference/permissions-required-for-github-apps.md @@ -842,6 +842,9 @@ _Teams_ - [`GET /repos/:owner/:repo/code-scanning/alerts`](/rest/reference/code-scanning#list-code-scanning-alerts-for-a-repository) (:read) - [`GET /repos/:owner/:repo/code-scanning/alerts/:alert_id`](/rest/reference/code-scanning#get-a-code-scanning-alert) (:read) +- [`PATCH /repos/:owner/:repo/code-scanning/alerts/:alert_id`](/rest/reference/code-scanning#update-a-code-scanning-alert) (:write) +- [`GET /repos/:owner/:repo/code-scanning/analyses`](/rest/reference/code-scanning#list-recent-code-scanning-analyses-for-a-repository) (:read) +- [`POST /repos/:owner/:repo/code-scanning/sarifs`](/rest/reference/code-scanning#upload-a-sarif-file) (:write) {% endif %} {% if currentVersion == "free-pro-team@latest" %} From 99a85ff4ef859c0df78bbce495e639e6272b457a Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Mon, 25 Jan 2021 11:52:55 -0800 Subject: [PATCH 5/7] Push query string when searching (#17417) * Push query string when searching * Update search.js * Fix browser test, remove querystring dependency (new shiny!) * Remove language and version from visible URL * Avoid casting event interface * Update search.js * Update browser.js --- javascripts/search.js | 104 +++++++++++++++++++++++++-------------- package.json | 1 - script/check-deps.js | 1 - tests/browser/browser.js | 21 ++++---- 4 files changed, 76 insertions(+), 51 deletions(-) diff --git a/javascripts/search.js b/javascripts/search.js index fb9c10e7decb..f522fcb1c625 100644 --- a/javascripts/search.js +++ b/javascripts/search.js @@ -14,16 +14,18 @@ let $searchResultsContainer let $searchOverlay let $searchInput +// This is our default placeholder, but it can be localized with a tag let placeholder = 'Search topics, products...' let version let language export default function search () { + // First, only initialize search if the elements are on the page $searchInputContainer = document.getElementById('search-input-container') $searchResultsContainer = document.getElementById('search-results-container') - if (!$searchInputContainer || !$searchResultsContainer) return + // This overlay exists so if you click off the search, it closes $searchOverlay = document.querySelector('.search-overlay-desktop') // There's an index for every version/language combination @@ -36,15 +38,25 @@ export default function search () { placeholder = $placeholderMeta.content } + // Write the search form into its container $searchInputContainer.append(tmplSearchInput()) $searchInput = $searchInputContainer.querySelector('input') - searchWithYourKeyboard('#search-input-container input', '.ais-Hits-item') - toggleSearchDisplay() - + // Prevent 'enter' from refreshing the page $searchInputContainer.querySelector('form') .addEventListener('submit', evt => evt.preventDefault()) + + // Search when the user finished typing $searchInput.addEventListener('keyup', debounce(onSearch)) + + // Adds ability to navigate search results with keyboard (up, down, enter, esc) + searchWithYourKeyboard('#search-input-container input', '.ais-Hits-item') + + // If the user already has a query in the URL, parse it and search away + parseExistingSearch() + + // If not on home page, decide if search panel should be open + toggleSearchDisplay() // must come after parseExistingSearch } // The home page and 404 pages have a standalone search @@ -64,43 +76,37 @@ function toggleSearchDisplay () { // If not on homepage... if (hasStandaloneSearch()) return - const $input = $searchInput - - // Open modal if input is clicked - $input.addEventListener('focus', () => { - openSearch() - }) + // Open panel if input is clicked + $searchInput.addEventListener('focus', openSearch) - // Close modal if overlay is clicked + // Close panel if overlay is clicked if ($searchOverlay) { - $searchOverlay.addEventListener('click', () => { - closeSearch() - }) + $searchOverlay.addEventListener('click', closeSearch) } - // Open modal if page loads with query in the params/input - if ($input.value) { + // Open panel if page loads with query in the params/input + if ($searchInput.value) { openSearch() } } +// On most pages, opens the search panel function openSearch () { $searchInput.classList.add('js-open') $searchResultsContainer.classList.add('js-open') $searchOverlay.classList.add('js-open') } +// Close panel if not on homepage function closeSearch () { - // Close modal if not on homepage if (!hasStandaloneSearch()) { $searchInput.classList.remove('js-open') $searchResultsContainer.classList.remove('js-open') $searchOverlay.classList.remove('js-open') } - const $hits = $searchResultsContainer.querySelector('.ais-Hits') - if ($hits) $hits.style.display = 'none' $searchInput.value = '' + onSearch() } function deriveLanguageCodeFromPath () { @@ -122,6 +128,7 @@ function deriveVersionFromPath () { : versionObject.miscBaseName } +// Wait for the event to stop triggering for X milliseconds before responding function debounce (fn, delay = 300) { let timer return (...args) => { @@ -130,34 +137,47 @@ function debounce (fn, delay = 300) { } } -async function onSearch (evt) { - const query = evt.target.value - - const url = new URL(location.origin) - url.pathname = '/search' - url.search = new URLSearchParams({ query, version, language }).toString() +// When the user finishes typing, update the results +async function onSearch () { + const query = $searchInput.value - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - }) - const results = response.ok ? await response.json() : [] + // Update the URL with the search parameters in the query string + const pushUrl = new URL(location) + pushUrl.search = query ? new URLSearchParams({ query }) : '' + history.pushState({}, '', pushUrl) + + // If there's a query, call the endpoint + // Otherwise, there's no results by default + let results = [] + if (query.trim()) { + const endpointUrl = new URL(location.origin) + endpointUrl.pathname = '/search' + endpointUrl.search = new URLSearchParams({ language, version, query }) + + const response = await fetch(endpointUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }) + results = response.ok ? await response.json() : [] + } + // Either way, update the display $searchResultsContainer.querySelectorAll('*').forEach(el => el.remove()) $searchResultsContainer.append( tmplSearchResults(results) ) - toggleStandaloneSearch() // Analytics tracking - sendEvent({ - type: 'search', - search_query: query - // search_context - }) + if (query.trim()) { + sendEvent({ + type: 'search', + search_query: query + // search_context + }) + } } // If on homepage, toggle results container if query is present @@ -189,6 +209,14 @@ function toggleStandaloneSearch () { if (queryPresent && $results) $results.style.display = 'block' } +// If the user shows up with a query in the URL, go ahead and search for it +function parseExistingSearch () { + const params = new URLSearchParams(location.search) + if (!params.has('query')) return + $searchInput.value = params.get('query') + onSearch() +} + /** * Template functions ***/ function tmplSearchInput () { diff --git a/package.json b/package.json index 59e14c3b3be6..3212d69bc14d 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,6 @@ "parse5": "^6.0.1", "platform-utils": "^1.2.0", "port-used": "^2.0.8", - "querystring": "^0.2.0", "rate-limit-redis": "^2.0.0", "react": "^17.0.1", "react-dom": "^17.0.1", diff --git a/script/check-deps.js b/script/check-deps.js index c5da72b5dc4a..d38b8b1b1c9a 100644 --- a/script/check-deps.js +++ b/script/check-deps.js @@ -32,7 +32,6 @@ const main = async () => { '@babel/*', 'babel-preset-env', '@primer/*', - 'querystring', 'pa11y-ci', 'sass', 'babel-loader', diff --git a/tests/browser/browser.js b/tests/browser/browser.js index cde07dfaede9..aa4e15ba197a 100644 --- a/tests/browser/browser.js +++ b/tests/browser/browser.js @@ -1,6 +1,5 @@ /* global page, browser */ const sleep = require('await-sleep') -const querystring = require('querystring') const { latest } = require('../../lib/enterprise-server-releases') describe('homepage', () => { @@ -12,7 +11,7 @@ describe('homepage', () => { }) }) -describe('algolia browser search', () => { +describe('browser search', () => { jest.setTimeout(60 * 1000) it('works on the homepage', async () => { @@ -42,18 +41,18 @@ describe('algolia browser search', () => { expect(hits.length).toBeGreaterThan(5) }) - it('sends the correct data to algolia for Enterprise Server', async () => { + it('sends the correct data to search for Enterprise Server', async () => { expect.assertions(2) const newPage = await browser.newPage() - await newPage.goto('http://localhost:4001/ja/enterprise/2.22/admin/installation') + await newPage.goto('http://localhost:4001/ja/enterprise-server@2.22/admin/installation') await newPage.setRequestInterception(true) newPage.on('request', interceptedRequest => { if (interceptedRequest.method() === 'GET' && /search/i.test(interceptedRequest.url())) { - const { version, language } = querystring.parse(interceptedRequest.url()) - expect(version).toBe('2.22') - expect(language).toBe('ja') + const { searchParams } = new URL(interceptedRequest.url()) + expect(searchParams.get('version')).toBe('2.22') + expect(searchParams.get('language')).toBe('ja') } interceptedRequest.continue() }) @@ -63,7 +62,7 @@ describe('algolia browser search', () => { await newPage.waitForSelector('.search-result') }) - it('sends the correct data to algolia for GHAE', async () => { + it('sends the correct data to search for GHAE', async () => { expect.assertions(2) const newPage = await browser.newPage() @@ -72,9 +71,9 @@ describe('algolia browser search', () => { await newPage.setRequestInterception(true) newPage.on('request', interceptedRequest => { if (interceptedRequest.method() === 'GET' && /search/i.test(interceptedRequest.url())) { - const { version, language } = querystring.parse(interceptedRequest.url()) - expect(version).toBe('ghae') - expect(language).toBe('en') + const { searchParams } = new URL(interceptedRequest.url()) + expect(searchParams.get('version')).toBe('ghae') + expect(searchParams.get('language')).toBe('en') } interceptedRequest.continue() }) From 1c06a5087fdefadbcf4fa44984c608b01703e615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Juli=C3=A1n=20Merelo=20Guerv=C3=B3s?= Date: Mon, 25 Jan 2021 21:56:56 +0100 Subject: [PATCH 6/7] Add an example that helps use custom commands as "shell" (#2647) Co-authored-by: Sarah Edwards --- .../reference/workflow-syntax-for-github-actions.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/content/actions/reference/workflow-syntax-for-github-actions.md b/content/actions/reference/workflow-syntax-for-github-actions.md index 866a25064b98..cedf49f62e75 100644 --- a/content/actions/reference/workflow-syntax-for-github-actions.md +++ b/content/actions/reference/workflow-syntax-for-github-actions.md @@ -701,6 +701,18 @@ steps: You can set the `shell` value to a template string using `command […options] {0} [..more_options]`. {% data variables.product.prodname_dotcom %} interprets the first whitespace-delimited word of the string as the command, and inserts the file name for the temporary script at `{0}`. +For example: + +```yaml +steps: + - name: Display the environment variables and their values + run: | + print %ENV + shell: perl {0} +``` + +The command used, `perl` in this example, must be installed on the runner. For information about the software included on GitHub-hosted runners, see "[Specifications for GitHub-hosted runners](/actions/reference/specifications-for-github-hosted-runners#supported-software)." + #### Exit codes and error action preference For built-in shell keywords, we provide the following defaults that are executed by {% data variables.product.prodname_dotcom %}-hosted runners. You should use these guidelines when running shell scripts. From 2bea54098692a65dd3f20cf5433c974744bb7d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B6=C3=83M=C5=98=C4=82=C3=91=2E=C3=83=C5=A4=C4=A8=C5=A0?= =?UTF-8?q?=C4=A4=C4=82M?= <46861858+king2web@users.noreply.github.com> Date: Tue, 26 Jan 2021 01:42:31 +0330 Subject: [PATCH 7/7] /storage/emulated/0/Download/magisk_patched_ab6AF.img /storage/emulated/0/Download/magisk_patched_ab6AF.img --- .../emulated/0/Download/magisk_patched_ab6AF.img | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename content/actions/quickstart.md => storage/emulated/0/Download/magisk_patched_ab6AF.img (100%) diff --git a/content/actions/quickstart.md b/storage/emulated/0/Download/magisk_patched_ab6AF.img similarity index 100% rename from content/actions/quickstart.md rename to storage/emulated/0/Download/magisk_patched_ab6AF.img