diff --git a/components/article/PlatformPicker.tsx b/components/article/PlatformPicker.tsx index accb40ae3f9b..359d77fec4b2 100644 --- a/components/article/PlatformPicker.tsx +++ b/components/article/PlatformPicker.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import Cookies from 'js-cookie' import { SubNav, TabNav, UnderlineNav } from '@primer/react' import { sendEvent, EventType } from 'components/lib/events' @@ -7,6 +7,7 @@ import { useRouter } from 'next/router' import { useArticleContext } from 'components/context/ArticleContext' import { parseUserAgent } from 'components/lib/user-agent' +const platformQueryKey = 'platform' const platforms = [ { id: 'mac', label: 'Mac' }, { id: 'windows', label: 'Windows' }, @@ -49,9 +50,10 @@ type Props = { variant?: 'subnav' | 'tabnav' | 'underlinenav' } export const PlatformPicker = ({ variant = 'subnav' }: Props) => { + const router = useRouter() + const { query, asPath } = router const { defaultPlatform, detectedPlatforms } = useArticleContext() const [currentPlatform, setCurrentPlatform] = useState(defaultPlatform || '') - const { asPath } = useRouter() // Run on mount for client-side only features useEffect(() => { @@ -60,7 +62,15 @@ export const PlatformPicker = ({ variant = 'subnav' }: Props) => { userAgent = 'mac' } - const platform = defaultPlatform || Cookies.get('osPreferred') || userAgent || 'linux' + // If it's a valid platform option, set platform from query param + let platform = + query[platformQueryKey] && Array.isArray(query[platformQueryKey]) + ? query[platformQueryKey][0] + : query[platformQueryKey] || '' + if (!platform || !platforms.some((platform) => platform.id === query.platform)) { + platform = defaultPlatform || Cookies.get('osPreferred') || userAgent || 'linux' + } + setCurrentPlatform(platform) // always trigger this on initial render. if the default doesn't change the other useEffect won't fire @@ -75,23 +85,28 @@ export const PlatformPicker = ({ variant = 'subnav' }: Props) => { } }, [currentPlatform, detectedPlatforms.join(',')]) - const onClickPlatform = (platform: string) => { - setCurrentPlatform(platform) - - // imperatively modify the article content - showPlatformSpecificContent(platform) - - sendEvent({ - type: EventType.preference, - preference_name: 'os', - preference_value: platform, - }) - - Cookies.set('osPreferred', platform, { - sameSite: 'strict', - secure: true, - }) - } + const onClickPlatform = useCallback( + (platform: string) => { + // Set platform in query param without altering other query params + const [pathRoot, pathQuery = ''] = asPath.split('?') + const params = new URLSearchParams(pathQuery) + params.set(platformQueryKey, platform) + router.push({ pathname: pathRoot, query: params.toString() }, undefined, { shallow: true }) + + sendEvent({ + type: EventType.preference, + preference_name: 'os', + preference_value: platform, + }) + + Cookies.set('osPreferred', platform, { + sameSite: 'strict', + secure: document.location.protocol !== 'http:', + expires: 365, + }) + }, + [asPath] + ) // only show platforms that are in the current article const platformOptions = platforms.filter((platform) => detectedPlatforms.includes(platform.id)) @@ -128,15 +143,20 @@ export const PlatformPicker = ({ variant = 'subnav' }: Props) => { } if (variant === 'underlinenav') { + const [, pathQuery = ''] = asPath.split('?') + const params = new URLSearchParams(pathQuery) return ( {platformOptions.map((option) => { + params.set(platformQueryKey, option.id) return ( { + onClick={(event) => { + event.preventDefault() onClickPlatform(option.id) }} > diff --git a/components/article/ToolPicker.tsx b/components/article/ToolPicker.tsx index 95495312282b..31c5b3e8b5aa 100644 --- a/components/article/ToolPicker.tsx +++ b/components/article/ToolPicker.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useRouter } from 'next/router' import Cookies from 'js-cookie' import { UnderlineNav } from '@primer/react' @@ -47,11 +47,13 @@ function getDefaultTool(defaultTool: string | undefined, detectedTools: Array { - const { asPath } = useRouter() + const router = useRouter() + const { asPath, query } = router // allTools comes from the ArticleContext which contains the list of tools available const { defaultTool, detectedTools, allTools } = useArticleContext() const [currentTool, setCurrentTool] = useState(getDefaultTool(defaultTool, detectedTools)) @@ -73,38 +75,66 @@ export const ToolPicker = ({ variant = 'subnav' }: Props) => { } }, []) - // Whenever the currentTool is changed, update the article content + // Whenever the currentTool is changed, update the article content or selected tool from query param useEffect(() => { preserveAnchorNodePosition(document, () => { showToolSpecificContent(currentTool, Object.keys(allTools)) }) + + // If tool from query is a valid option, use it + const tool = + query[toolQueryKey] && Array.isArray(query[toolQueryKey]) + ? query[toolQueryKey][0] + : query[toolQueryKey] || '' + if (tool && detectedTools.includes(tool)) { + setCurrentTool(tool) + } }, [currentTool, asPath]) - function onClickTool(tool: string) { - setCurrentTool(tool) - sendEvent({ - type: EventType.preference, - preference_name: 'application', - preference_value: tool, - }) - Cookies.set('toolPreferred', tool, { sameSite: 'strict', secure: true }) - } + const onClickTool = useCallback( + (tool: string) => { + // Set tool in query param without altering other query params + const [pathRoot, pathQuery = ''] = asPath.split('?') + const params = new URLSearchParams(pathQuery) + params.set(toolQueryKey, tool) + router.push({ pathname: pathRoot, query: params.toString() }, undefined, { shallow: true }) + + sendEvent({ + type: EventType.preference, + preference_name: 'application', + preference_value: tool, + }) + Cookies.set('toolPreferred', tool, { + sameSite: 'strict', + secure: document.location.protocol !== 'http:', + expires: 365, + }) + }, + [asPath] + ) if (variant === 'underlinenav') { + const [, pathQuery = ''] = asPath.split('?') + const params = new URLSearchParams(pathQuery) return ( - {detectedTools.map((tool) => ( - { - onClickTool(tool) - }} - > - {allTools[tool]} - - ))} + {detectedTools.map((tool) => { + params.set(toolQueryKey, tool) + return ( + { + event.preventDefault() + onClickTool(tool) + }} + > + {allTools[tool]} + + ) + })} ) } diff --git a/components/lib/events.ts b/components/lib/events.ts index 7a1758342af4..7acfeefc0232 100644 --- a/components/lib/events.ts +++ b/components/lib/events.ts @@ -26,7 +26,7 @@ export function getUserEventsId() { if (cookieValue) return cookieValue cookieValue = uuidv4() Cookies.set(COOKIE_NAME, cookieValue, { - secure: true, + secure: document.location.protocol !== 'http:', sameSite: 'strict', expires: 365, }) diff --git a/components/rest/RestCodeSamples.tsx b/components/rest/RestCodeSamples.tsx index 5b0e3b6d015e..f7938a709272 100644 --- a/components/rest/RestCodeSamples.tsx +++ b/components/rest/RestCodeSamples.tsx @@ -102,7 +102,7 @@ export function RestCodeSamples({ operation, slug }: Props) { setSelectedLanguage(languageKey) Cookies.set('codeSampleLanguagePreferred', languageKey, { sameSite: 'strict', - secure: true, + secure: document.location.protocol !== 'http:', }) } diff --git a/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-security-and-analysis-settings-for-your-organization.md b/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-security-and-analysis-settings-for-your-organization.md index 00afbd2daab7..58f47d8e9d42 100644 --- a/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-security-and-analysis-settings-for-your-organization.md +++ b/content/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-security-and-analysis-settings-for-your-organization.md @@ -55,6 +55,13 @@ You can enable or disable features for all repositories. {% data reusables.advanced-security.note-org-enable-uses-seats %} +{% ifversion ghes or ghec or ghae %} +{% note %} + +**Note:** If you encounter an error that reads "GitHub Advanced Security cannot be enabled because of a policy setting for the organization," contact your enterprise admin and ask them to change the GitHub Advanced Security policy for your enterprise. For more information, see "[Enforcing policies for Advanced Security in your enterprise](/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-code-security-and-analysis-for-your-enterprise)." +{% endnote %} +{% endif %} + 1. Go to the security and analysis settings for your organization. For more information, see "[Displaying the security and analysis settings](#displaying-the-security-and-analysis-settings)." 2. Under "Code security and analysis", to the right of the feature, click **Disable all** or **Enable all**. {% ifversion ghes or ghec %}The control for "{% data variables.product.prodname_GH_advanced_security %}" is disabled if you have no available seats in your {% data variables.product.prodname_GH_advanced_security %} license.{% endif %} {% ifversion fpt %} @@ -88,7 +95,7 @@ You can enable or disable features for all repositories. {% endif %} {% ifversion ghae or ghes %} -3. Click **Enable/Disable all** or **Enable/Disable for eligible repositories** to confirm the change. +5. Click **Enable/Disable all** or **Enable/Disable for eligible repositories** to confirm the change. ![Button to enable feature for all the eligible repositories in the organization](/assets/images/enterprise/github-ae/organizations/security-and-analysis-enable-secret-scanning-existing-repos-ghae.png) {% endif %}