| | import { languageKeys } from '@/languages/lib/languages-server' |
| | import { allVersionKeys } from '@/versions/lib/all-versions' |
| | import { productIds } from '@/products/lib/all-products' |
| | import { allTools } from '@/tools/lib/all-tools' |
| | import { contentTypesEnum } from '@/frame/lib/frontmatter' |
| |
|
| | const versionPattern = '^\\d+(\\.\\d+)?(\\.\\d+)?$' |
| |
|
| | const context = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['event_id', 'user', 'version', 'created', 'path'], |
| | properties: { |
| | |
| | event_id: { |
| | type: 'string', |
| | description: 'The unique identifier of the event.', |
| | format: 'uuid', |
| | }, |
| | user: { |
| | type: 'string', |
| | description: |
| | "The unique identifier of the current user performing the event. Please use randomly generated values or hashed values; we don't want to be able to look up in a database.", |
| | format: 'uuid', |
| | }, |
| | version: { |
| | type: 'string', |
| | description: 'The version of the event schema.', |
| | pattern: versionPattern, |
| | }, |
| | created: { |
| | type: 'string', |
| | format: 'date-time', |
| | description: 'The time we created the event; please reference UTC.', |
| | }, |
| | page_event_id: { |
| | type: 'string', |
| | description: 'The id of the corresponding `page` event.', |
| | format: 'uuid', |
| | }, |
| |
|
| | |
| | referrer: { |
| | type: 'string', |
| | description: 'The browser value of `document.referrer`.', |
| | format: 'uri-reference', |
| | }, |
| | title: { |
| | type: 'string', |
| | description: 'The browser value of `document.title`.', |
| | }, |
| | href: { |
| | type: 'string', |
| | description: 'The browser value of `location.href`.', |
| | format: 'uri', |
| | }, |
| | hostname: { |
| | type: 'string', |
| | description: 'The browser value of `location.hostname.`', |
| | format: 'uri-reference', |
| | }, |
| | path: { |
| | type: 'string', |
| | description: 'The browser value of `location.pathname`.', |
| | format: 'uri-reference', |
| | }, |
| | search: { |
| | type: 'string', |
| | description: 'The browser value of `location.search`.', |
| | }, |
| | hash: { |
| | type: 'string', |
| | description: 'The browser value of `location.hash`.', |
| | }, |
| | path_language: { |
| | type: 'string', |
| | description: 'The language the user is viewing, from the URL path.', |
| | enum: languageKeys, |
| | }, |
| | path_version: { |
| | type: 'string', |
| | description: 'The GitHub version of the docs, from the URL path.', |
| | enum: allVersionKeys, |
| | }, |
| | path_product: { |
| | type: 'string', |
| | description: 'The GitHub product the docs are for, from the URL path.', |
| | enum: productIds.concat(['homepage']), |
| | }, |
| | path_article: { |
| | type: 'string', |
| | description: 'The article path without language or version, from the URL path.', |
| | }, |
| | page_document_type: { |
| | type: 'string', |
| | description: 'The generic page document type based on URL path.', |
| | enum: ['homepage', 'early-access', 'product', 'category', 'subcategory', 'article'], |
| | }, |
| | page_type: { |
| | type: 'string', |
| | description: 'Optional page type from the content frontmatter.', |
| | enum: ['overview', 'quick_start', 'tutorial', 'how_to', 'reference', 'rai'], |
| | }, |
| | content_type: { |
| | type: 'string', |
| | description: 'Optional content type from the content frontmatter (EDI content models).', |
| | enum: contentTypesEnum, |
| | }, |
| | status: { |
| | type: 'number', |
| | description: 'The HTTP response status code of the main page HTML.', |
| | minimum: 0, |
| | maximum: 999, |
| | }, |
| | is_logged_in: { |
| | type: 'boolean', |
| | description: 'Anonymous -- whether the user has github.com cookies set.', |
| | }, |
| | dotcom_user: { |
| | type: 'string', |
| | description: 'The cookie value of dotcom_user', |
| | }, |
| | is_staff: { |
| | type: 'boolean', |
| | description: 'The cookie value of staffonly', |
| | }, |
| |
|
| | |
| | os: { |
| | type: 'string', |
| | description: 'The type of operating system the user is working with.', |
| | enum: ['windows', 'mac', 'linux', 'ios', 'android', 'cros', 'other'], |
| | default: 'other', |
| | }, |
| | os_version: { |
| | type: 'string', |
| | description: 'The version of the operating system the user is using.', |
| | }, |
| | browser: { |
| | type: 'string', |
| | description: 'The type of browser the user is browsing with.', |
| | enum: ['chrome', 'safari', 'firefox', 'edge', 'opera', 'other'], |
| | default: 'other', |
| | }, |
| | browser_version: { |
| | type: 'string', |
| | description: 'The version of the browser the user is browsing with.', |
| | }, |
| | is_headless: { |
| | type: 'boolean', |
| | }, |
| | viewport_width: { |
| | type: 'number', |
| | description: 'The viewport width, not the overall device size.', |
| | minimum: 0, |
| | }, |
| | viewport_height: { |
| | type: 'number', |
| | description: 'The viewport height, not the overall device height.', |
| | minimum: 0, |
| | }, |
| | screen_width: { |
| | type: 'number', |
| | description: 'The screen width of the device.', |
| | minimum: 0, |
| | }, |
| | screen_height: { |
| | type: 'number', |
| | description: 'The screen height of the device.', |
| | minimum: 0, |
| | }, |
| | pixel_ratio: { |
| | type: 'number', |
| | description: 'The device pixel ratio.', |
| | minimum: 0, |
| | }, |
| | ip: { |
| | type: 'string', |
| | description: 'The IP address of the user.', |
| | }, |
| | user_agent: { |
| | type: 'string', |
| | description: 'The raw user agent string from the browser.', |
| | }, |
| |
|
| | |
| | timezone: { |
| | type: 'number', |
| | description: 'The timezone the user is in, as `new Date().getTimezoneOffset() / -60`.', |
| | }, |
| | user_language: { |
| | type: 'string', |
| | description: 'The browser value of `navigator.language`.', |
| | }, |
| |
|
| | |
| | os_preference: { |
| | type: 'string', |
| | enum: ['linux', 'mac', 'windows'], |
| | description: 'The os for examples selected by the user.', |
| | }, |
| | application_preference: { |
| | type: 'string', |
| | enum: Object.keys(allTools), |
| | description: 'The application selected by the user.', |
| | }, |
| | color_mode_preference: { |
| | enum: ['dark', 'light', 'auto', 'auto:dark', 'auto:light'], |
| | description: 'The color mode selected by the user.', |
| | }, |
| | code_display_preference: { |
| | enum: ['beside', 'inline'], |
| | description: 'How the user prefers to view code examples.', |
| | }, |
| |
|
| | |
| | experiment_variation: { |
| | type: 'string', |
| | description: 'The variation this user we bucketed in is in, such as control or treatment.', |
| | }, |
| |
|
| | |
| | event_group_key: { |
| | type: 'string', |
| | description: 'A enum indentifier (e.g. "ask-ai") used to put events into a specific group.', |
| | }, |
| | event_group_id: { |
| | type: 'string', |
| | description: |
| | 'A unique id (uuid) that can be used to identify a group of events made by a user during the same session.', |
| | }, |
| | }, |
| | } |
| |
|
| | const page = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['type', 'context'], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^page$', |
| | }, |
| | }, |
| | } |
| |
|
| | const exit = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['type', 'context'], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^exit$', |
| | }, |
| | exit_render_duration: { |
| | type: 'number', |
| | description: 'How long the server took to render the page content, in seconds.', |
| | minimum: 0.001, |
| | }, |
| | exit_first_paint: { |
| | type: 'number', |
| | minimum: 0.001, |
| | description: |
| | 'The duration until `first-contentful-paint`, in seconds. Informs CSS performance.', |
| | }, |
| | exit_dom_interactive: { |
| | type: 'number', |
| | minimum: 0.001, |
| | description: |
| | 'The duration until `PerformanceNavigationTiming.domInteractive`, in seconds. Informs JavaScript loading performance.', |
| | }, |
| | exit_dom_complete: { |
| | type: 'number', |
| | minimum: 0.001, |
| | description: |
| | 'The duration until `PerformanceNavigationTiming.domComplete`, in seconds. Informs JavaScript execution performance.', |
| | }, |
| | exit_visit_duration: { |
| | type: 'number', |
| | minimum: 0.001, |
| | description: |
| | 'The duration of exit.timestamp - page.timestamp, in seconds. Informs bounce rate.', |
| | }, |
| | exit_scroll_length: { |
| | type: 'number', |
| | minimum: 0, |
| | maximum: 1, |
| | description: 'The percentage of how far the user scrolled on the page.', |
| | }, |
| | exit_scroll_flip: { |
| | type: 'number', |
| | minimum: 0, |
| | description: 'The number of times the scroll direction changes.', |
| | }, |
| | }, |
| | } |
| |
|
| | const keyboard = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['pressed_key', 'pressed_on'], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^keyboard$', |
| | }, |
| | pressed_key: { |
| | type: 'string', |
| | description: 'The key the user pressed.', |
| | }, |
| | pressed_on: { |
| | type: 'string', |
| | description: 'The element/identifier the user pressed the key on.', |
| | }, |
| | }, |
| | } |
| |
|
| | const link = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['type', 'context', 'link_url'], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^link$', |
| | }, |
| | link_url: { |
| | type: 'string', |
| | format: 'uri', |
| | description: |
| | 'The href of the anchor tag the user clicked, or the page or object they directed their browser to.', |
| | }, |
| | link_samesite: { |
| | type: 'boolean', |
| | description: 'If the link stays on docs.github.com.', |
| | }, |
| | link_samepage: { |
| | type: 'boolean', |
| | description: 'If the link stays on the same page (hash link).', |
| | }, |
| | link_container: { |
| | type: 'string', |
| | enum: [ |
| | 'header', |
| | 'nav', |
| | 'breadcrumbs', |
| | 'title', |
| | 'lead', |
| | 'notifications', |
| | 'article', |
| | 'alert', |
| | 'toc', |
| | 'footer', |
| | 'static', |
| | ], |
| | description: 'The part of the page where the user clicked the link.', |
| | }, |
| | }, |
| | } |
| |
|
| | const hover = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['type', 'context', 'hover_url'], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^hover$', |
| | }, |
| | hover_url: { |
| | type: 'string', |
| | format: 'uri', |
| | description: |
| | 'The href of the anchor tag the user hovered, or the page or object they directed their browser to.', |
| | }, |
| | hover_samesite: { |
| | type: 'boolean', |
| | description: 'If the hover link stays on docs.github.com.', |
| | }, |
| | }, |
| | } |
| |
|
| | const search = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['type', 'context', 'search_query'], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^search$', |
| | }, |
| | search_query: { |
| | type: 'string', |
| | description: 'The actual text content of the query string the user sent to the service.', |
| | }, |
| | search_context: { |
| | type: 'string', |
| | description: 'Any additional search context, such as component searched.', |
| | }, |
| | search_client: { |
| | type: 'string', |
| | description: 'The client name identifier when the request is not from docs.github.com.', |
| | }, |
| | }, |
| | } |
| |
|
| | const searchResult = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: [ |
| | 'type', |
| | 'context', |
| | 'search_result_query', |
| | 'search_result_index', |
| | 'search_result_total', |
| | 'search_result_rank', |
| | 'search_result_url', |
| | ], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^searchResult$', |
| | }, |
| | search_result_query: { |
| | type: 'string', |
| | description: 'The query the user searched for.', |
| | }, |
| | search_result_index: { |
| | type: 'number', |
| | description: 'The order position of the user selected search result.', |
| | }, |
| | search_result_total: { |
| | type: 'number', |
| | description: 'The total number of search results we returned for the query.', |
| | }, |
| | search_result_rank: { |
| | type: 'number', |
| | description: |
| | 'The rank score of the order position of the search result, example: `(total - index) / total`.', |
| | }, |
| | search_result_url: { |
| | type: 'string', |
| | description: 'The destination url of the search result the user selected.', |
| | }, |
| | }, |
| | } |
| |
|
| | const aiSearchResult = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: [ |
| | 'type', |
| | 'context', |
| | 'ai_search_result_links_json', |
| | 'ai_search_result_provided_answer', |
| | 'ai_search_result_response_status', |
| | ], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^aiSearchResult$', |
| | }, |
| | ai_search_result_links_json: { |
| | type: 'string', |
| | description: |
| | 'Dynamic JSON string of an array of "link" objects in the form: [{ "type": "reference" | "inline", "url": "https://..", "product": "issues" | "pages" | ... }, ...]', |
| | }, |
| | ai_search_result_provided_answer: { |
| | type: 'boolean', |
| | description: 'Whether the GPT was able to answer the query.', |
| | }, |
| | ai_search_result_response_status: { |
| | type: 'number', |
| | description: 'The status code of the GPT response.', |
| | }, |
| | ai_search_result_connected_event_id: { |
| | type: 'string', |
| | description: 'The id of the corresponding CSE copilot conversation event.', |
| | }, |
| | }, |
| | } |
| |
|
| | const survey = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['type', 'context', 'survey_vote'], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^survey$', |
| | }, |
| | survey_vote: { |
| | type: 'boolean', |
| | description: 'Whether the user found the page helpful.', |
| | }, |
| | survey_comment: { |
| | type: 'string', |
| | description: 'Any textual comments the user wanted to provide.', |
| | }, |
| | survey_email: { |
| | type: 'string', |
| | format: 'email', |
| | description: "The user's email address, if the user provided and consented.", |
| | }, |
| | survey_rating: { |
| | type: 'number', |
| | description: |
| | 'The computed rating of the quality of the survey comment. Used for spam filtering and quality control.', |
| | }, |
| | survey_comment_language: { |
| | type: 'string', |
| | description: |
| | 'The guessed language of the survey comment. The guessed language is very inaccurate when the string contains fewer than 3 or 4 words.', |
| | }, |
| | survey_connected_event_id: { |
| | type: 'string', |
| | description: 'The id of the corresponding CSE copilot conversation event.', |
| | }, |
| | }, |
| | } |
| |
|
| | const experiment = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['type', 'context', 'experiment_name', 'experiment_variation'], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^experiment$', |
| | }, |
| | experiment_name: { |
| | type: 'string', |
| | description: 'The test that this event is part of.', |
| | }, |
| | experiment_variation: { |
| | type: 'string', |
| | description: 'The variation this user we bucketed in is in, such as control or treatment.', |
| | }, |
| | experiment_success: { |
| | type: 'boolean', |
| | default: true, |
| | description: 'Whether or not the user successfully performed the test goal.', |
| | }, |
| | }, |
| | } |
| |
|
| | const clipboard = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['type', 'context', 'clipboard_operation'], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^clipboard$', |
| | }, |
| | clipboard_operation: { |
| | type: 'string', |
| | description: 'Which clipboard operation the user is performing.', |
| | enum: ['copy', 'paste', 'cut'], |
| | }, |
| | clipboard_target: { |
| | type: 'string', |
| | description: 'How the user got the contents into their clipboard.', |
| | }, |
| | }, |
| | } |
| |
|
| | const print = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['type', 'context'], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^print$', |
| | }, |
| | }, |
| | } |
| |
|
| | const preference = { |
| | type: 'object', |
| | additionalProperties: false, |
| | required: ['type', 'context', 'preference_name', 'preference_value'], |
| | properties: { |
| | context, |
| | type: { |
| | type: 'string', |
| | pattern: '^preference$', |
| | }, |
| | preference_name: { |
| | type: 'string', |
| | enum: ['application', 'color_mode', 'os', 'code_display'], |
| | description: 'The preference name, such as os, application, or color_mode', |
| | }, |
| | preference_value: { |
| | type: 'string', |
| | enum: [ |
| | |
| | ...Object.keys(allTools), |
| | |
| | 'dark', |
| | 'light', |
| | 'auto', |
| | 'auto:dark', |
| | 'auto:light', |
| | |
| | 'linux', |
| | 'mac', |
| | 'windows', |
| | |
| | 'beside', |
| | 'inline', |
| | ], |
| | description: 'The application, color_mode, os, or code_display selected by the user.', |
| | }, |
| | }, |
| | } |
| |
|
| | const validation = { |
| | type: 'object', |
| | additionalProperties: false, |
| | properties: { |
| | event_id: { type: 'string', format: 'uuid' }, |
| | version: { type: 'string', pattern: versionPattern }, |
| | created: { type: 'string', format: 'date-time' }, |
| | raw: { type: 'string' }, |
| | |
| | keyword: { type: 'string' }, |
| | instance_path: { type: 'string' }, |
| | schema_path: { type: 'string' }, |
| | params: { type: 'string' }, |
| | property_name: { type: 'string' }, |
| | message: { type: 'string' }, |
| | schema: { type: 'string' }, |
| | parent_schema: { type: 'string' }, |
| | data: { type: 'string' }, |
| | }, |
| | } |
| |
|
| | |
| | export const schemas = { |
| | page, |
| | exit, |
| | keyboard, |
| | link, |
| | hover, |
| | search, |
| | searchResult, |
| | aiSearchResult, |
| | survey, |
| | experiment, |
| | clipboard, |
| | print, |
| | preference, |
| | validation, |
| | } |
| |
|
| | export const hydroNames = { |
| | page: 'docs.v0.PageEvent', |
| | exit: 'docs.v0.ExitEvent', |
| | keyboard: 'docs.v0.KeyboardEvent', |
| | link: 'docs.v0.LinkEvent', |
| | hover: 'docs.v0.HoverEvent', |
| | search: 'docs.v0.SearchEvent', |
| | searchResult: 'docs.v0.SearchResultEvent', |
| | aiSearchResult: 'docs.v0.AISearchResultEvent', |
| | survey: 'docs.v0.SurveyEvent', |
| | experiment: 'docs.v0.ExperimentEvent', |
| | clipboard: 'docs.v0.ClipboardEvent', |
| | print: 'docs.v0.PrintEvent', |
| | preference: 'docs.v0.PreferenceEvent', |
| | validation: 'docs.v0.ValidationEvent', |
| | } as Record<keyof typeof schemas, string> |
| |
|
| | const schemasKeys = Object.keys(schemas) |
| | const hydroNamesKeys = Object.keys(hydroNames) |
| | if ( |
| | schemasKeys.length !== hydroNamesKeys.length || |
| | !schemasKeys.every((k) => hydroNamesKeys.includes(k)) |
| | ) { |
| | throw new Error("The keys in 'schemas' doesn't match with the keys in 'hydroNames'") |
| | } |
| |
|