| 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'") |
| } |
|
|