diff --git a/.github/workflows/index-autocomplete-search.yml b/.github/workflows/index-autocomplete-search.yml index 8d116a66ca5f..09375d0e7b0a 100644 --- a/.github/workflows/index-autocomplete-search.yml +++ b/.github/workflows/index-autocomplete-search.yml @@ -1,7 +1,7 @@ name: Index autocomplete search in Elasticsearch -# **What it does**: Indexes autocomplete data (general and AI search) into Elasticsearch. -# **Why we have it**: So we can power the APIs for autocomplete. +# **What it does**: Indexes AI search autocomplete data into Elasticsearch. +# **Why we have it**: So we can power the APIs for AI search autocomplete. # **Who does it impact**: docs-engineering on: @@ -40,11 +40,6 @@ jobs: if: ${{ github.event_name == 'pull_request' }} run: curl --fail --retry-connrefused --retry 5 -I http://localhost:9200 - - name: Run general auto-complete indexing - env: - ELASTICSEARCH_URL: ${{ github.event_name == 'pull_request' && 'http://localhost:9200' || secrets.ELASTICSEARCH_URL }} - run: npm run index-general-autocomplete -- docs-internal-data - - name: Run AI search auto-complete indexing env: ELASTICSEARCH_URL: ${{ github.event_name == 'pull_request' && 'http://localhost:9200' || secrets.ELASTICSEARCH_URL }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 548f7fd56939..716bfff8a4a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,6 @@ jobs: - shielding # - tests # - tools - - tracking - versions - webhooks - workflows diff --git a/content/billing/managing-your-billing/about-usage-reports.md b/content/billing/managing-your-billing/about-usage-reports.md new file mode 100644 index 000000000000..a1cdab9a49cc --- /dev/null +++ b/content/billing/managing-your-billing/about-usage-reports.md @@ -0,0 +1,54 @@ +--- +title: About usage reports +intro: 'Learn how to request and understand a report that shows detailed {% data variables.product.github %} usage and billing information for your account.' +versions: + feature: enhanced-billing-platform +type: how_to +topics: + - Enterprise + - Team +permissions: '{% data reusables.permissions.enhanced-billing-platform %}' +product: '{% data reusables.billing.enhanced-billing-platform-product %}' +--- + +The usage report shows detailed information about your account’s {% data variables.product.github %} usage, including how much of each SKU was used and the resulting billable amount. + +To generate a usage report, see [AUTOTITLE](/billing/managing-your-billing/gathering-insights-on-your-spending). + +## Usage report fields + +The usage report contains the following fields. + +| Field | Description | +|---------------------------|-------------| +| `date` | The day that the usage occurred. All usage is logged in UTC. | +| `product` | The {% data variables.product.github %} product that was used. | +| `sku` | The specific {% data variables.product.github %} product SKU that was used. | +| `quantity` | The amount of the SKU that was used. | +| `unit_type` | The unit of measurement for the product SKU. | +| `applied_cost_per_quantity` | The unit cost of the product SKU. | +| `gross_amount` | The amount of the product SKU that was used. | +| `discount_amount` | The amount of usage that was discounted. Usage that is discounted as part of your account’s included usage is reflected in this field. Also includes discounts for {% data variables.product.prodname_actions %} usage for standard {% data variables.product.github %}-hosted runners in public repositories and for self-hosted runners. | +| `net_amount` | The billable amount of usage after applying the `discount_amount`. This is the amount that your account will be billed. `gross_amount - discount_amount = net_amount`. | +| `username` | The user associated with the usage, if applicable. | +| `organization` | The organization associated with the usage, if applicable. | +| `repository` | The repository associated with the usage, if applicable. | +| `workflow_path` | The path of the {% data variables.product.prodname_actions %} workflow that generated the usage, if applicable. | +| `cost_center_name` | The cost center associated with the usage, if applicable. | + +### Deprecated report fields + +{% data variables.product.github %} aims to minimize changes to the usage report structure, however at times the report structure or fields may change. + +| Deprecated field | Replacement | +|--------------------|---------------------| +| `usage_at` | Refer to `date` instead. | +| `workflow_name` | Refer to `workflow_path` instead. | + +## How usage is summarized + +To reduce the size of the report, similar usage entries are grouped and totaled. The report summarizes the `quantity`, `gross_amount`, `discount_amount`, and `net_amount` fields based on the combination of the following values: `date`, `sku`, `username`, `workflow_path`, `repository`, `cost_center_name`. + +## Receiving the report + +Usage reports are sent via email to the default email address associated with your {% data variables.product.github %} account. diff --git a/content/billing/managing-your-billing/index.md b/content/billing/managing-your-billing/index.md index cdad208c2020..d2e798a69e92 100644 --- a/content/billing/managing-your-billing/index.md +++ b/content/billing/managing-your-billing/index.md @@ -19,6 +19,7 @@ children: - /adding-licenses-to-an-organization - /roles-for-the-new-billing-platform - /estimating-spending + - /about-usage-reports - /gathering-insights-on-your-spending - /charging-business-units - /preventing-overspending diff --git a/content/copilot/using-github-copilot/code-review/using-copilot-code-review.md b/content/copilot/using-github-copilot/code-review/using-copilot-code-review.md index a815ae71404a..cf555509794d 100644 --- a/content/copilot/using-github-copilot/code-review/using-copilot-code-review.md +++ b/content/copilot/using-github-copilot/code-review/using-copilot-code-review.md @@ -28,13 +28,13 @@ The current functionality and availability of the two types of review is summari {% rowheaders %} -| | Review selection | Review changes | -|------------------|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Available in | {% data variables.product.prodname_vscode %} | {% data variables.product.prodname_vscode %} and the {% data variables.product.github %} website | +| | Review selection | Review changes | +|------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Available in | {% data variables.product.prodname_vscode %} | {% data variables.product.prodname_vscode %} and the {% data variables.product.github %} website | | Premium/standard feature | Standard feature available to all {% data variables.product.prodname_copilot_short %} subscribers | Premium feature. Available with the {% data variables.copilot.copilot_pro_short %}, {% data variables.copilot.copilot_pro_plus_short %}, {% data variables.copilot.copilot_business_short %}, and {% data variables.copilot.copilot_enterprise_short %} plans. Per-person monthly quota applies. | -| Description | Initial review of a highlighted section of code with feedback and suggestions | Deeper review of all changes | -| Language support | All | C, C#, C++, Go, Java, JavaScript, Kotlin, Markdown, Python, Ruby, Swift, TypeScript

{% data variables.release-phases.public_preview_caps %} support for HTML and Text. | -| Custom coding guidelines support | No | Yes, see [Customizing {% data variables.product.prodname_copilot_short %}'s reviews with coding guidelines](#customizing-copilots-reviews-with-coding-guidelines) | +| Description | Initial review of a highlighted section of code with feedback and suggestions | Deeper review of all changes | +| Language support | All | All | +| Custom coding guidelines support | No | Yes, see [Customizing {% data variables.product.prodname_copilot_short %}'s reviews with coding guidelines](#customizing-copilots-reviews-with-coding-guidelines) | {% endrowheaders %} diff --git a/data/reusables/copilot/policies-for-dotcom.md b/data/reusables/copilot/policies-for-dotcom.md index 89beb382a92d..ae2e4cf35077 100644 --- a/data/reusables/copilot/policies-for-dotcom.md +++ b/data/reusables/copilot/policies-for-dotcom.md @@ -1,5 +1,2 @@ * **Opt in to user feedback collection:** If enabled, users can provide feedback on {% data variables.product.prodname_copilot_short %} pull request summaries. For more information, see [AUTOTITLE](/enterprise-cloud@latest/copilot/github-copilot-enterprise/copilot-pull-request-summaries/creating-a-pull-request-summary-with-github-copilot). -* **Opt in to preview features:** If enabled, users can test new {% data variables.product.prodname_copilot_short %} features that are not yet generally available. Be aware that previews of features may have flaws, and the features may be changed or discontinued at any time. Current previews of {% data variables.product.prodname_copilot_short %} features include: - - * Experimental languages in {% data variables.product.prodname_copilot_short %} code review. See [AUTOTITLE](/copilot/using-github-copilot/code-review/using-copilot-code-review). - * {% data variables.copilot.copilot_spaces %}. See [AUTOTITLE](/copilot/using-github-copilot/copilot-spaces/about-organizing-and-sharing-context-with-copilot-spaces). +* **Opt in to preview features:** If enabled, users can test new {% data variables.product.prodname_copilot_short %} features that are not yet generally available. Be aware that previews of features may have flaws, and the features may be changed or discontinued at any time. Current previews of {% data variables.product.prodname_copilot_short %} features include {% data variables.copilot.copilot_spaces %}. See [AUTOTITLE](/copilot/using-github-copilot/copilot-spaces/about-organizing-and-sharing-context-with-copilot-spaces). diff --git a/data/ui.yml b/data/ui.yml index e4e48a60ca46..d5e6fa9dea6b 100644 --- a/data/ui.yml +++ b/data/ui.yml @@ -25,7 +25,6 @@ release_notes: banner_text: GitHub began rolling these changes out to enterprises on search: input: - aria_label: Open search overlay placeholder: Search or ask {{icon}} Copilot placeholder_no_icon: Search or ask Copilot shortcut: Type {{icon}} to search @@ -320,17 +319,7 @@ alerts: WARNING: Warning TIP: Tip CAUTION: Caution -domain_edit: - name: Domain name - edit: Edit - edit_your: Edit your domain name - experimental: Experimental - your_name: Your domain name - cancel: Cancel - save: Save - snippet_about: Updating will include the new domain name in all code snippets across GitHub Docs. - learn_more: Learn more - submission_failed: Submission failed. Please try again in a minute. + cookbook_landing: spotlight: Spotlight explore_articles: Explore {{ number }} prompt articles diff --git a/package.json b/package.json index ce09ea6255aa..bdb8eda6b9d6 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,7 @@ "general-search-scrape-server": "cross-env NODE_ENV=production PORT=4002 MINIMAL_RENDER=true CHANGELOG_DISABLED=true tsx src/frame/server.ts", "ghes-release-scrape-with-server": "cross-env GHES_RELEASE=1 start-server-and-test general-search-scrape-server 4002 general-search-scrape", "general-search-scrape-with-server": "cross-env NODE_OPTIONS='--max_old_space_size=8192' start-server-and-test general-search-scrape-server 4002 general-search-scrape", - "index": "tsx src/search/scripts/index/index-cli autocomplete docs-internal-data", "index-ai-search-autocomplete": "tsx src/search/scripts/index/index-cli ai-search-autocomplete", - "index-general-autocomplete": "tsx src/search/scripts/index/index-cli general-autocomplete", "index-general-search": "tsx src/search/scripts/index/index-cli general-search", "index-test-fixtures": "./src/search/scripts/index-test-fixtures.sh", "labeler": "tsx .github/actions/labeler/labeler.ts", diff --git a/src/content-render/unified/processor.js b/src/content-render/unified/processor.js index 0f0c565270d1..62674a283c7a 100644 --- a/src/content-render/unified/processor.js +++ b/src/content-render/unified/processor.js @@ -27,7 +27,6 @@ import wrapProceduralImages from './wrap-procedural-images.js' import parseInfoString from './parse-info-string.js' import annotate from './annotate.js' import alerts from './alerts.js' -import replaceDomain from './replace-domain.js' import removeHtmlComments from 'remark-remove-comments' import remarkStringify from 'remark-stringify' @@ -49,7 +48,6 @@ export function createProcessor(context) { .use(headingLinks) .use(codeHeader) .use(annotate) - .use(replaceDomain) .use(highlight, { languages: { ...common, graphql, dockerfile, http, groovy, erb, powershell }, subset: false, diff --git a/src/content-render/unified/replace-domain.js b/src/content-render/unified/replace-domain.js deleted file mode 100644 index 21f224be25ac..000000000000 --- a/src/content-render/unified/replace-domain.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * This makes it so that the `github.com` or `HOSTNAME` in a code snippet - * becomes replacable. - */ - -import { visit } from 'unist-util-visit' - -// Don't use `g` on these regexes -const VALID_REPLACEMENTS = [[/\bHOSTNAME\b/, 'HOSTNAME']] - -const CODE_FENCE_KEYWORD = 'replacedomain' - -const matcher = (node) => { - return ( - node.type === 'element' && - node.tagName === 'pre' && - node.children[0]?.data?.meta[CODE_FENCE_KEYWORD] - ) -} - -export default function alerts() { - return (tree) => { - visit(tree, matcher, (node) => { - const code = node.children[0].children[0].value - for (const [regex, replacement] of VALID_REPLACEMENTS) { - if (regex.test(code)) { - const codeTag = node.children[0] - const replacements = codeTag.properties['data-replacedomain'] || [] - if (!replacements.includes(replacement)) { - replacements.push(replacement) - codeTag.properties['data-replacedomain'] = replacements - } - } - } - }) - } -} diff --git a/src/fixtures/fixtures/content/get-started/markdown/index.md b/src/fixtures/fixtures/content/get-started/markdown/index.md index 6fe218b5e7c9..6e88f4d2485f 100644 --- a/src/fixtures/fixtures/content/get-started/markdown/index.md +++ b/src/fixtures/fixtures/content/get-started/markdown/index.md @@ -10,6 +10,5 @@ children: - /permissions - /code-annotations - /alerts - - /replace-domain - /html-comments --- diff --git a/src/fixtures/fixtures/content/get-started/markdown/replace-domain.md b/src/fixtures/fixtures/content/get-started/markdown/replace-domain.md deleted file mode 100644 index 8bba2655fb21..000000000000 --- a/src/fixtures/fixtures/content/get-started/markdown/replace-domain.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: Replace domain -intro: This demonstrates code snippets that have host names that can be replaced. -versions: - fpt: '*' - ghes: '*' - ghec: '*' -type: how_to ---- - -## Overview - -If you have an article with code snippets that have the `replacedomain` -annotation on its code fence, that means the page *might* take the current -user's cookie (indicating their personal hostname) and replace that within -the code snippet. - -## Shell code snippet (on) - -```sh replacedomain -curl https://HOSTNAME/api/v1 -``` - -## Shell code snippet (off) - -```sh -curl https://HOSTNAME/api/v2 -``` - -## JavaScript code snippet (on) - -```js replacedomain -await fetch("https://HOSTNAME/api/v1") -``` - -## JavaScript code snippet (off) - -```js -await fetch("https://HOSTNAME/api/v2") -``` - -## Not always there - -In this next code snippet, the `HOSTNAME` only appears if the current -version is `ghes`. That should be fine. - -```text replacedomain copy -ssh handle@{% ifversion ghes %}HOSTNAME{% else %}github.com{% endif %} -``` diff --git a/src/fixtures/fixtures/data/ui.yml b/src/fixtures/fixtures/data/ui.yml index e4e48a60ca46..d5e6fa9dea6b 100644 --- a/src/fixtures/fixtures/data/ui.yml +++ b/src/fixtures/fixtures/data/ui.yml @@ -25,7 +25,6 @@ release_notes: banner_text: GitHub began rolling these changes out to enterprises on search: input: - aria_label: Open search overlay placeholder: Search or ask {{icon}} Copilot placeholder_no_icon: Search or ask Copilot shortcut: Type {{icon}} to search @@ -320,17 +319,7 @@ alerts: WARNING: Warning TIP: Tip CAUTION: Caution -domain_edit: - name: Domain name - edit: Edit - edit_your: Edit your domain name - experimental: Experimental - your_name: Your domain name - cancel: Cancel - save: Save - snippet_about: Updating will include the new domain name in all code snippets across GitHub Docs. - learn_more: Learn more - submission_failed: Submission failed. Please try again in a minute. + cookbook_landing: spotlight: Spotlight explore_articles: Explore {{ number }} prompt articles diff --git a/src/fixtures/tests/playwright-rendering.spec.ts b/src/fixtures/tests/playwright-rendering.spec.ts index 3c1c30957a17..660869fd07bd 100644 --- a/src/fixtures/tests/playwright-rendering.spec.ts +++ b/src/fixtures/tests/playwright-rendering.spec.ts @@ -844,35 +844,3 @@ test.describe('translations', () => { await expect(page).toHaveURL('/ja/get-started/start-your-journey/hello-world') }) }) - -test.describe('view pages with custom domain cookie', () => { - test('view article page', async ({ page }) => { - await page.goto( - '/enterprise-server@latest/get-started/markdown/replace-domain?ghdomain=example.ghe.com', - ) - - const content = page.locator('pre') - await expect(content.nth(0)).toHaveText(/curl https:\/\/example.ghe.com\/api\/v1/) - await expect(content.nth(1)).toHaveText(/curl https:\/\/HOSTNAME\/api\/v2/) - await expect(content.nth(2)).toHaveText('await fetch("https://example.ghe.com/api/v1")') - await expect(content.nth(3)).toHaveText('await fetch("https://HOSTNAME/api/v2")') - - // Now switch to enterprise-cloud, where replacedomain should not be used - await page.getByLabel('Select GitHub product version').click() - await page.getByLabel('Enterprise Cloud', { exact: true }).click() - - await expect(content.nth(0)).toHaveText(/curl https:\/\/HOSTNAME\/api\/v1/) - await expect(content.nth(1)).toHaveText(/curl https:\/\/HOSTNAME\/api\/v2/) - await expect(content.nth(2)).toHaveText('await fetch("https://HOSTNAME/api/v1")') - await expect(content.nth(3)).toHaveText('await fetch("https://HOSTNAME/api/v2")') - - // Again switch back to enterprise server again - await page.getByLabel('Select GitHub product version').click() - await page.getByLabel('Enterprise Server 3.').first().click() - - await expect(content.nth(0)).toHaveText(/curl https:\/\/example.ghe.com\/api\/v1/) - await expect(content.nth(1)).toHaveText(/curl https:\/\/HOSTNAME\/api\/v2/) - await expect(content.nth(2)).toHaveText('await fetch("https://example.ghe.com/api/v1")') - await expect(content.nth(3)).toHaveText('await fetch("https://HOSTNAME/api/v2")') - }) -}) diff --git a/src/frame/components/DefaultLayout.tsx b/src/frame/components/DefaultLayout.tsx index 4142e923de62..69f8db09638e 100644 --- a/src/frame/components/DefaultLayout.tsx +++ b/src/frame/components/DefaultLayout.tsx @@ -13,7 +13,6 @@ import { useTranslation } from '@/languages/components/useTranslation' import { Breadcrumbs } from '@/frame/components/page-header/Breadcrumbs' import { useLanguages } from '@/languages/components/LanguagesContext' import { ClientSideLanguageRedirect } from './ClientSideLanguageRedirect' -import { DomainNameEditProvider } from '@/links/components/useEditableDomainContext' import { SearchOverlayContextProvider } from '@/search/components/context/SearchOverlayContext' const MINIMAL_RENDER = Boolean(JSON.parse(process.env.MINIMAL_RENDER || 'false')) @@ -76,99 +75,97 @@ export const DefaultLayout = (props: Props) => { } return ( - - - - {error === '404' ? ( - {t('oops')} - ) : (!isHomepageVersion && page.fullTitle) || - (currentPathWithoutLanguage.includes('enterprise-server') && page.fullTitle) ? ( - {page.fullTitle} - ) : null} - - {/* For Google and Bots */} - - {page.hidden && } - {Object.values(languages) - .filter((lang) => lang.code !== router.locale) - .map((variant) => { - return ( - - ) - })} - - {/* For local site search indexing */} - {page.topics.length > 0 && } - - {/* For analytics events */} - {router.locale && } - {currentVersion && } - {currentProduct && } - {relativePath && ( - - )} - {page.type && } - {page.documentType && } - {status && } - - {/* OpenGraph data */} - {page.fullTitle && ( - <> - - - - - - - )} - {/* Twitter Meta Tags */} - - - - - {page.introPlainText && } - - - - Skip to main content - -
- -
- {isHomepageVersion ? null : } - {/* Need to set an explicit height for sticky elements since we also - set overflow to auto */} -
-
- - - - {props.children} -
-
- - - + + {error === '404' ? ( + {t('oops')} + ) : (!isHomepageVersion && page.fullTitle) || + (currentPathWithoutLanguage.includes('enterprise-server') && page.fullTitle) ? ( + {page.fullTitle} + ) : null} + + {/* For Google and Bots */} + + {page.hidden && } + {Object.values(languages) + .filter((lang) => lang.code !== router.locale) + .map((variant) => { + return ( + -
-
+ ) + })} + + {/* For local site search indexing */} + {page.topics.length > 0 && } + + {/* For analytics events */} + {router.locale && } + {currentVersion && } + {currentProduct && } + {relativePath && ( + + )} + {page.type && } + {page.documentType && } + {status && } + + {/* OpenGraph data */} + {page.fullTitle && ( + <> + + + + + + + )} + {/* Twitter Meta Tags */} + + + + + {page.introPlainText && } + + + + Skip to main content + +
+ +
+ {isHomepageVersion ? null : } + {/* Need to set an explicit height for sticky elements since we also + set overflow to auto */} +
+
+ + + + {props.children} +
+
+ + + +
- - +
+ ) } diff --git a/src/frame/components/article/ArticlePage.tsx b/src/frame/components/article/ArticlePage.tsx index 5532845fa7f2..882edee0003d 100644 --- a/src/frame/components/article/ArticlePage.tsx +++ b/src/frame/components/article/ArticlePage.tsx @@ -21,7 +21,6 @@ import { Breadcrumbs } from '@/frame/components/page-header/Breadcrumbs' import { Link } from '@/frame/components/Link' import { useTranslation } from '@/languages/components/useTranslation' import { LinkPreviewPopover } from '@/links/components/LinkPreviewPopover' -import { ReplaceDomain } from '@/links/components/replace-domain' const ClientSideRefresh = dynamic(() => import('@/frame/components/ClientSideRefresh'), { ssr: false, @@ -104,7 +103,6 @@ export const ArticlePage = () => { {isDev && } {router.pathname.includes('/rest/') && } - {currentLayout === 'inline' ? ( <>
@@ -105,14 +101,6 @@ export function HeaderSearchAndWidgets({ width, isSearchOpen, SearchButton }: Pr <> - {showDomainNameEdit && ( - <> - - - - - - )} )} {signupCTAVisible && ( diff --git a/src/frame/components/page-header/OldHeaderSearchAndWidgets.tsx b/src/frame/components/page-header/OldHeaderSearchAndWidgets.tsx index 3f8a2c049593..7e64bed8fe7d 100644 --- a/src/frame/components/page-header/OldHeaderSearchAndWidgets.tsx +++ b/src/frame/components/page-header/OldHeaderSearchAndWidgets.tsx @@ -1,11 +1,9 @@ -import { Suspense } from 'react' import cx from 'classnames' import { SearchIcon, XIcon, KebabHorizontalIcon, LinkExternalIcon } from '@primer/octicons-react' import { IconButton, ActionMenu, ActionList } from '@primer/react' import { LanguagePicker } from '@/languages/components/LanguagePicker' import { useTranslation } from '@/languages/components/useTranslation' -import DomainNameEdit from '@/links/components/DomainNameEdit' import { OldSearchInput } from '@/search/components/input/OldSearchInput' import { VersionPicker } from '@/versions/components/VersionPicker' import { DEFAULT_VERSION, useVersion } from '@/versions/components/useVersion' @@ -29,8 +27,6 @@ export function OldHeaderSearchAndWidgets({ isSearchOpen, setIsSearchOpen, width hasAccount === false && // don't show if `null` (currentVersion === DEFAULT_VERSION || currentVersion === 'enterprise-cloud@latest') - const showDomainNameEdit = currentVersion.startsWith('enterprise-server@') - return (
{/* */} @@ -151,14 +147,6 @@ export function OldHeaderSearchAndWidgets({ isSearchOpen, setIsSearchOpen, width <> - {showDomainNameEdit && ( - <> - - - - - - )} )} {signupCTAVisible && ( diff --git a/src/frame/middleware/index.ts b/src/frame/middleware/index.ts index e59cce6aaac1..571d52930757 100644 --- a/src/frame/middleware/index.ts +++ b/src/frame/middleware/index.ts @@ -62,7 +62,6 @@ import mockVaPortal from './mock-va-portal' import dynamicAssets from '@/assets/middleware/dynamic-assets' import generalSearchMiddleware from '@/search/middleware/general-search-middleware' import shielding from '@/shielding/middleware' -import tracking from '@/tracking/middleware' import { MAX_REQUEST_TIMEOUT } from '@/frame/lib/constants.js' const { NODE_ENV } = process.env @@ -200,7 +199,6 @@ export default function (app: Express) { } // ** Possible early exits after cookies ** - app.use(tracking) // *** Headers *** app.set('etag', false) // We will manage our own ETags if desired diff --git a/src/frame/stylesheets/index.scss b/src/frame/stylesheets/index.scss index 7adc4e7f165e..d109279e492e 100644 --- a/src/frame/stylesheets/index.scss +++ b/src/frame/stylesheets/index.scss @@ -14,4 +14,3 @@ @import "src/content-render/stylesheets/index.scss"; @import "src/links/stylesheets/hover-card.scss"; -@import "src/links/stylesheets/domain-edit.scss"; diff --git a/src/links/components/DomainNameEdit.tsx b/src/links/components/DomainNameEdit.tsx deleted file mode 100644 index a724683e6921..000000000000 --- a/src/links/components/DomainNameEdit.tsx +++ /dev/null @@ -1,305 +0,0 @@ -import { useEffect, useState, useRef } from 'react' -import { useRouter } from 'next/router' -import { BeakerIcon } from '@primer/octicons-react' - -import { useTranslation } from '@/languages/components/useTranslation' -import { Box, Flash, FormControl, Spinner, TextInput } from '@primer/react' -import { Dialog } from '@primer/react/experimental' -import { useEditableDomainName } from './useEditableDomainContext' -import { sendEvent } from '@/events/components/events' -import { EventType } from '@/events/types' - -type Props = { - xs?: boolean -} - -const EXPERIMENT_NAME = 'domain_edit' - -const QUERY_STRING_KEY = 'ghdomain' // Must match the middleware - -const TRANSLATION_NAMESPACE = 'domain_edit' - -export default function DomainNameEdit({ xs }: Props) { - const { t } = useTranslation(TRANSLATION_NAMESPACE) - - const { asPath } = useRouter() - - const { domainName, setDomainName } = useEditableDomainName() - const [localName, setLocalName] = useState('') - useEffect(() => { - setLocalName(domainName) - }, [domainName]) - - const [open, setOpen] = useState(false) - useEffect(() => { - function handler(event: MouseEvent) { - if (event.target) { - const target = event.target as HTMLElement | SVGSVGElement - if ( - (target.tagName === 'BUTTON' && target.classList.contains('replacedomain-edit')) || - (target.tagName === 'SPAN' && target.classList.contains('replacedomain-text')) || - (target.tagName === 'svg' && - target.parentElement && - target.parentElement.classList.contains('replacedomain-edit')) || - (target.tagName === 'path' && - target.parentElement && - target.parentElement.parentElement && - target.parentElement.parentElement.classList.contains('replacedomain-edit')) - ) { - setOpen(true) - - sendEvent({ - type: EventType.experiment, - experiment_name: EXPERIMENT_NAME, - experiment_variation: 'opened', - experiment_success: true, - }) - } - } - } - const main = document.querySelector('#main-content') - if (main) { - main.addEventListener('click', handler) - } - - return () => { - if (main) { - main.removeEventListener('click', handler) - } - } - }, [asPath]) - - useEffect(() => { - if (document.querySelectorAll('code[data-replacedomain]').length > 0) { - sendEvent({ - type: EventType.experiment, - experiment_name: EXPERIMENT_NAME, - experiment_variation: 'available', - experiment_success: true, - }) - } - }, [asPath]) - - const nameInputRef = useRef(null) - useEffect(() => { - if (open) { - if (nameInputRef.current) { - nameInputRef.current.focus() - } - } - }, [open]) - - const [submissionFailed, setSubmissionFailed] = useState(false) - const [loading, setLoading] = useState(false) - - function handlSubmit(name: string) { - const searchParams = new URLSearchParams({ [QUERY_STRING_KEY]: name }) - setLoading(true) - fetch(`/__tracking__?${searchParams.toString()}`) - .then((response) => { - if (response.ok) { - setOpen(false) - setSubmissionFailed(false) - setDomainName(localName.trim().toLowerCase()) - } else { - setSubmissionFailed(true) - } - - sendEvent({ - type: EventType.experiment, - experiment_name: EXPERIMENT_NAME, - experiment_variation: 'saved', - experiment_success: true, - }) - }) - .finally(() => { - setLoading(false) - }) - } - - const validationError = getValidationError(localName) - - return ( -
- {open && ( - - {t('edit_your')}{' '} - - {t('experimental')} - - - } - width="large" - footerButtons={[ - { buttonType: 'default', content: t('cancel'), onClick: () => setOpen(false) }, - { - buttonType: 'primary', - type: 'button', - onClick: () => { - handlSubmit(localName.trim()) - }, - content: loading ? ( - <> - {t('save')} - - ) : ( - t('save') - ), - disabled: !!validationError || loading, - }, - ]} - onClose={() => { - setOpen(false) - - sendEvent({ - type: EventType.experiment, - experiment_name: EXPERIMENT_NAME, - experiment_variation: 'closed', - experiment_success: true, - }) - }} - aria-labelledby="header" - > -
{ - event.preventDefault() - if (!validationError) { - handlSubmit(localName.trim()) - } - }} - > - - - {t('name')} - setLocalName(event.target.value)} - validationStatus={localName.trim() && validationError ? 'error' : undefined} - sx={{ width: '100%' }} - /> - {localName.trim() && validationError && ( - {validationError} - )} - - - - - - - -
- )} - - {/* Deliberately commented out until we decide to include this on all pages */} - {/* setOpen(true)} - returnFocusRef={returnFocusRef} - /> */} -
- ) -} - -function getValidationError(domainName: string) { - const clean = domainName.trim().toLowerCase() - if (/\s/.test(clean)) { - return 'Whitespace' - } - // if (clean === 'hostname' || !clean) { - // return 'Empty' - // } - if (clean === 'github.com' || clean === 'api.github.com') { - return "Can't be github.com" - } - return null -} - -function SubmissionError({ error }: { error: boolean }) { - const { t } = useTranslation(TRANSLATION_NAMESPACE) - if (error) { - return ( - -

{t('submission_failed')}

-
- ) - } - return null -} - -/* Deliberately commented out until we decide to include this on all pages */ -// function DisplayAndToggle({ -// xs, -// domainNames, -// trigger, -// returnFocusRef, -// }: { -// xs?: boolean -// domainNames: string[] -// trigger: () => void -// returnFocusRef: React.RefObject -// }) { -// const { t } = useTranslation(TRANSLATION_NAMESPACE) -// return ( -// -// -// -// {t('name')}: -// {' '} -// {domainNames.length ? domainNames[0] : DEFAULT} -// -// -// -// ) -// } - -function LearnMoreSnippet() { - const { t } = useTranslation(TRANSLATION_NAMESPACE) - return ( - -

- {t('snippet_about')}{' '} - - {t('learn_more')} - -

-
- ) -} diff --git a/src/links/components/pen-icon.tsx b/src/links/components/pen-icon.tsx deleted file mode 100644 index cd5671fb3742..000000000000 --- a/src/links/components/pen-icon.tsx +++ /dev/null @@ -1,20 +0,0 @@ -export function createPenSVG(): SVGSVGElement { - const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') - svg.setAttribute('focusable', 'false') - svg.setAttribute('role', 'img') - svg.setAttribute('viewBox', '0 0 16 16') - svg.setAttribute('width', '16') - svg.setAttribute('height', '16') - svg.setAttribute('fill', 'currentColor') - svg.setAttribute( - 'style', - 'display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible; color: var(--color-accent-fg); text-decoration: dashed underline;', - ) - - const PEN_PATH = `M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z` - const path = document.createElementNS('http://www.w3.org/2000/svg', 'path') - path.setAttribute('d', PEN_PATH) - svg.appendChild(path) - - return svg -} diff --git a/src/links/components/replace-domain.ts b/src/links/components/replace-domain.ts deleted file mode 100644 index 9751a08958c5..000000000000 --- a/src/links/components/replace-domain.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { useEffect } from 'react' -import { useRouter } from 'next/router' - -import { useVersion } from '@/versions/components/useVersion' -import { useEditableDomainName } from './useEditableDomainContext' -import { createPenSVG } from './pen-icon' - -// We only want to activate the replace-domain feature for these versions. -// This means that if you're on a version we don't want it activated on, -// even though you have a your-domain cookie, it *won't* replace the -// word HOSTNAME. -const REPLACEDOMAIN_VERSION_PREFIXES = ['enterprise-server@'] - -function replaceDomains(domain: string | null) { - document.querySelectorAll('pre code[data-replacedomain]').forEach((codeBlock) => { - const replaceDomain = codeBlock.dataset.replacedomain - if (!replaceDomain) return - const replaceDomains = replaceDomain.split(/\s/) - const spans = codeBlock.querySelectorAll('span[class*="-string"]') - if (spans.length) { - spans.forEach((span) => { - replaceInTextContent(span, replaceDomains, domain) - }) - } else { - replaceInTextContent(codeBlock, replaceDomains, domain) - } - replaceInClipboard(codeBlock, replaceDomains, domain) - }) -} - -function replaceInClipboard(element: HTMLElement, replaceDomains: string[], domain: string | null) { - if ( - element.parentElement && - element.parentElement.parentElement && - element.parentElement.parentElement.classList.contains('code-example') - ) { - const pre = - element.parentElement.parentElement.querySelector('pre[data-clipboard]') - const regex = new RegExp(`(${replaceDomains.join('|')})`) - if (pre && pre.textContent) { - if (!pre.dataset.originalText) { - pre.dataset.originalText = pre.textContent - } - if (domain) { - pre.textContent = pre.dataset.originalText.replace(regex, domain) - } else { - pre.textContent = pre.dataset.originalText - } - } - } -} - -function replaceInTextContent( - element: HTMLElement, - replaceDomains: string[], - domain: string | null, -) { - if (!element.textContent) return - - if (!element.querySelector('.replacedomain-text')) { - splitElementText(element, replaceDomains) - } - - if (domain !== null) { - element.querySelectorAll('.replacedomain-text').forEach((textSpan) => { - textSpan.textContent = domain - textSpan.classList.add('editable-domain') - }) - element.querySelectorAll('.replacedomain-edit').forEach((toggleElement) => { - toggleElement.classList.remove('visually-hidden') - }) - } else { - element.querySelectorAll('.replacedomain-text').forEach((textSpan) => { - if (element.dataset.replacedomain) { - textSpan.textContent = element.dataset.replacedomain - } - textSpan.classList.remove('editable-domain') - }) - element.querySelectorAll('.replacedomain-edit').forEach((toggleElement) => { - toggleElement.classList.remove('visually-hidden') - }) - } -} - -function splitElementText(element: HTMLElement, replaceDomains: string[]) { - const splitText = element.textContent!.split(new RegExp(`(${replaceDomains.join('|')})`)) - element.textContent = '' - for (const text of splitText) { - if (replaceDomains.includes(text)) { - element.appendChild(createEditWrapper(text)) - } else { - const span = document.createElement('span') - span.textContent = text - element.appendChild(span) - } - } -} - -function createEditWrapper(text: string): HTMLSpanElement { - const element = document.createElement('span') - element.classList.add('replacedomain-edit') - const span = document.createElement('span') - span.classList.add('replacedomain-text') - span.textContent = text - element.appendChild(span) - element.appendChild(createPenSVG()) - - return element -} - -export function ReplaceDomain() { - const { asPath } = useRouter() - const { domainName } = useEditableDomainName() - const { currentVersion } = useVersion() - - const enable = REPLACEDOMAIN_VERSION_PREFIXES.some((prefix) => currentVersion.startsWith(prefix)) - - useEffect(() => { - if (domainName) { - if (enable) { - replaceDomains(domainName.split(',')[0]) - } else { - replaceDomains(null) - } - } else if (enable) { - replaceDomains(null) - } - }, [asPath, enable, domainName]) - - return null -} diff --git a/src/links/components/useEditableDomainContext.tsx b/src/links/components/useEditableDomainContext.tsx deleted file mode 100644 index e11999b056d2..000000000000 --- a/src/links/components/useEditableDomainContext.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useContext, useEffect, createContext, useState } from 'react' -import Cookies from 'js-cookie' - -const COOKIE_KEY = 'github_domains' -const DEFAULT = '' - -type DomainNameEdit = { - domainName: string - setDomainName: (value: string) => void -} -const UseEditableDomainContext = createContext({ - domainName: '', - setDomainName: () => {}, -}) - -export function DomainNameEditProvider({ children }: { children: React.ReactNode }) { - const [name, setName] = useState(DEFAULT) - useEffect(() => { - const cookie = Cookies.get(COOKIE_KEY) - if (cookie) { - setName(cookie.split(',')[0]) - } - }, []) - - return ( - - {children} - - ) -} - -export const useEditableDomainName = () => { - const context = useContext(UseEditableDomainContext) - if (context === undefined) { - throw new Error('useEditableDomainName must be inside a DomainNameEditProvider') - } - return context -} diff --git a/src/links/stylesheets/domain-edit.scss b/src/links/stylesheets/domain-edit.scss deleted file mode 100644 index 3a6de25bba23..000000000000 --- a/src/links/stylesheets/domain-edit.scss +++ /dev/null @@ -1,38 +0,0 @@ -span.replacedomain-edit { - color: var(--fgColor-accent, var(--color-accent-fg, #0969da)); - font-weight: 600; - border-width: 1px; - border-style: solid solid dashed; - border-image: initial; - border-top-color: transparent; - border-right-color: transparent; - border-bottom-color: var( - --borderColor-default, - var(--color-border-default, #d0d7de) - ); - border-left-color: transparent; - padding-left: 2px; - padding-right: 2px; - cursor: pointer; - - :hover { - border-color: var( - --borderColor-default, - var(--color-border-default, #d0d7de) - ); - background-color: var( - --bgColor-default, - var(--color-canvas-default, #ffffff) - ); - } - - svg { - margin: 0 3px; - } -} - -span.editable-domain { - color: var(--color-accent-fg); - cursor: pointer; - // text-decoration: dashed underline; -} diff --git a/src/search/README.md b/src/search/README.md index e4035210b408..7ae3f25742fd 100644 --- a/src/search/README.md +++ b/src/search/README.md @@ -22,7 +22,7 @@ You can also query our search endpoint directly at: ## Types of search -Our backend currently supports 3 "types" of searching. +Our backend currently supports 2 "types" of searching. All searches accept a `query` param, e.g. `?query=how` and return results based on their type: @@ -30,11 +30,7 @@ All searches accept a `query` param, e.g. `?query=how` and return results based - Results: The pages of our sites that match the query, sorted by popularity - Example: Query = "clone" -> Results - Endpoint: `/api/search/v1` -2. **general autocomplete** - - Results: Potential terms that can be autocompleted from the query based on previous user searches - - Example: Query = "cl" -> A Result = "clone" - - Endpoint: `/api/search/autocomplete/v1` -3. **AI search autocomplete** +2. **AI search autocomplete** - Results: Human-readable full-sentence questions that best match the query. Questions are based on previous searches and popular pages - Example: Query = "How do I clone" -> A Result = "How do I clone a repository?" - Endpoint: `/api/search/ai-search-autocomplete/v1` @@ -66,7 +62,7 @@ The preferred way to build and sync the search indices is to do so via the [GitH ### Actions workflow files - [`.github/workflows/index-general-search.yml`](/.github/workflows/index-general-search.yml) - Populates search indices for **general search** using the `main` branch every four hours. Search indices are stored in an internal-only Elasticsearch instance. To run it manually, click "Run workflow" button in the Actions tab. -- [`.github/workflows/index-autocomplete-search.yml`](/.github/workflows/index-general-search.yml) - Populates search indices for both **general autocomplete** and **AI search autocomplete** using data from an internal repo. Runs daily. +- [`.github/workflows/index-autocomplete-search.yml`](/.github/workflows/index-general-search.yml) - Populates search indices for **AI search autocomplete** using data from an internal repo. Runs daily. ### Notable code files and directories diff --git a/src/search/components/input/SearchBarButton.tsx b/src/search/components/input/SearchBarButton.tsx index 43a6aa56f8bf..d8640750e61f 100644 --- a/src/search/components/input/SearchBarButton.tsx +++ b/src/search/components/input/SearchBarButton.tsx @@ -58,14 +58,14 @@ export function SearchBarButton({ isSearchOpen, setIsSearchOpen, params, searchB className={styles.searchIconButton} onClick={handleClick} tabIndex={0} - aria-label={t('search.input.aria_label')} + aria-label={t('search.input.placeholder_no_icon')} icon={SearchIcon} /> {/* On large and up the SearchBarButton is shown */}