diff --git a/docs/platforms/javascript/common/apis.mdx b/docs/platforms/javascript/common/apis.mdx new file mode 100644 index 00000000000000..c14e814d84c7ee --- /dev/null +++ b/docs/platforms/javascript/common/apis.mdx @@ -0,0 +1,993 @@ +--- +title: APIs +description: "Learn more about APIs of the SDK." +customCanonicalTag: "/platforms/javascript/apis/" +sidebar_order: 3 +--- + +This page shows all available top-level APIs of the SDK. You can use these APIs as the primary way to: + +- Configure the SDK after initialization +- Manually capture different types of events +- Enrich events with additional data +- ... and more! + +These APIs are functions that you can use as follows - they are all available on the top-level `Sentry` object: + +```javascript +import * as Sentry from "@sentry/browser"; + +Sentry.setTag("tag", "value"); +``` + +## Available APIs + + + +## Core APIs + + + Initialize the SDK with the given options. See{" "} + Options for the + options you can pass to `init`. + + + + Returns the currently active client. + + + + Make the given client the current client. You do not need this if you use + `init()`, this is only necessary if you are manually setting up a client. + + + + Returns the ID of the last sent error event. Note that this does not guarantee + that this event ID exists, as it may have been dropped along the way. + + + + Flushes all pending events. + + + + Returns true if the SDK is initialized & enabled. + + + + Flushes all pending events and disables the SDK. Note that this does not + remove any listeners the SDK may have set up. + + + Event | null | Promise", + }, + ]} +> + Adds an event processor to the SDK. An event processor receives every event + before it is sent to Sentry. It can either mutate the event (and return it) or + return `null` to discard the event. Event processors can also return a + promise, but it is recommended to use this only when necessary as it slows + down event processing. + + + Event processors added via `Sentry.addEventProcessor()` will be applied to all events in your application. + If you want to add an event processor that only applies to certain events, you can also add one to a scope as follows: + + + + Event processors added via `Sentry.addEventProcessor()` will be applied to all events in your current request. + If you want to add an event processor that only applies to certain events, you can also add one to a scope as follows: + + +```javascript +Sentry.withScope((scope) => { + scope.addEventProcessor((event) => { + // this will only be applied to events captured within this scope + return event; + }); + + Sentry.captureException(new Error("test")); +}); +``` + + + `beforeSend` and `beforeSendTransaction` are guaranteed to be run last, after all other event processors, (which means they get the final version of the event right before it's sent, hence the name). Event processors added with `addEventProcessor` are run in an undetermined order, which means changes to the event may still be made after the event processor runs. + + There can only be a single `beforeSend` / `beforeSendTransaction` processor, but you can add multiple event processors via `addEventProcessor()`. + + + + +Adds an integration to the SDK. This can be used to conditionally add +integrations after `Sentry.init()` has been called. Note that it is +recommended to pass integrations to `init` instead of calling this method, +where possible. + +See Integrations for +more information on how to use integrations. + + + + + Lazy load an integration. This expects the name to be e.g. `replayIntegration`. It will load the script from the CDN, and return a promise that resolves to the integration, which can then be added to the SDK using `addIntegration`: + +```javascript +Sentry.lazyLoadIntegration("replayIntegration") + .then((integration) => { + Sentry.addIntegration(integration); + }) + .catch((error) => { + // Make sure to handle errors here! + // This rejects e.g. if the CDN bundle cannot be loaded + }); +``` + +If you use a bundler, using e.g. `const { replayIntegration } = await import('@sentry/browser')` is recommended instead. + +See Integrations for +more information on how to use integrations. + + + +## Capturing Events + +", + description: + "Additional data that should be sent with the exception.", + }, + { + name: "tags", + type: "Record", + description: + "Additional tags that should be sent with the exception.", + }, + { name: "contexts", type: "Record>" }, + { name: "fingerprint", type: "string[]" }, + ], + }, + description: "Optional additional data to attach to the Sentry event.", + }, + ]} +> + Capture an exception event and send it to Sentry. Note that you can pass not + only `Error` objects, but also other objects as `exception` - in that case, + the SDK will attempt to serialize the object for you, and the stack trace will + be generated by the SDK and may be less accurate. + + +", + description: + "Additional data that should be sent with the exception.", + }, + { + name: "tags", + type: "Record", + description: + "Additional tags that should be sent with the exception.", + }, + { name: "contexts", type: "Record>" }, + { name: "fingerprint", type: "string[]" }, + ], + }, + description: "Optional additional data to attach to the Sentry event.", + }, +]} +> +Capture a message event and send it to Sentry. Optionally, instead of a +`CaptureContext`, you can also pass a `SeverityLevel` as second argument, e.g. +`"error"` or `"warning"`. + +Messages show up as issues on your issue stream, with the message as the issue name. + + + +## Enriching Events + + + Set a tag to be sent with Sentry events. + + + + Set multiple tags to be sent with Sentry events. + + + + Set a context to be sent with Sentry events. + + + + Set additional data to be sent with Sentry events. + + + + Set multiple additional data entries to be sent with Sentry events. + + + + Set a user to be sent with Sentry events. Set to `null` to unset the user. + + +", + description: + "Additional data that should be sent with the breadcrumb.", + }, + ], + }, + }, + { + name: "hint", + type: "Record", + description: + "A hint object containing additional information about the breadcrumb.", + }, + ]} +> + You can manually add breadcrumbs whenever something interesting happens. For + example, you might manually record a breadcrumb if the user authenticates or + another state change occurs. + + +## Tracing + +', description: 'Attributes to add to the span.' }, + { name: 'startTime', type: 'number', description: 'The timestamp to use for the span start. If not provided, the current time will be used.' }, + { name: 'op', type: 'string', description: 'The operation name for the span. This is used to group spans in the UI' }, + { name: 'forceTransaction', type: 'boolean', description: 'If true, the span will be forced to be sent as a transaction, even if it is not the root span.' }, + { name: 'parentSpan', type: 'Span | null', description: 'The parent span for the new span. If not provided, the current span will be used.' }, + { name: 'onlyIfParent', type: 'boolean', description: 'If true, the span will only be created if there is an active span.' }, + ] + }, + }, + { name: "callback", type: "(span: Span) => T", required: true }, +]}> + Starts a new span, that is active in the provided callback. + This span will be a child of the currently active span, if there is one. + +Any spans created inside of the callback will be children of this span. + +The started span will automatically be ended when the callback returns, and will thus measure the duration of the callback. The callback cann also be an async function. + + + ```javascript + // Synchronous example + Sentry.startSpan({ name: 'my-span' }, (span) => { + measureThis(); + }); + +// Asynchronous example +const status = await Sentry.startSpan({ name: 'my-span' }, async (span) => { +const status = await doSomething(); +return status; +}); + +```` + + +See Tracing Instrumentation for more information on how to work with spans. + + + +', description: 'Attributes to add to the span.' }, + { name: 'startTime', type: 'number', description: 'The timestamp to use for the span start. If not provided, the current time will be used.' }, + { name: 'op', type: 'string', description: 'The operation name for the span. This is used to group spans in the UI' }, + { name: 'forceTransaction', type: 'boolean', description: 'If true, the span will be forced to be sent as a transaction, even if it is not the root span.' }, + { name: 'parentSpan', type: 'Span | null', description: 'The parent span for the new span. If not provided, the current span will be used.' }, + { name: 'onlyIfParent', type: 'boolean', description: 'If true, the span will only be created if there is an active span.' }, + ] + }, + } +]}> + Starts a new span. This span will be a child of the currently active span, if there is one. + The returned span has to be ended manually via `span.end()` when the span is done. + + + ```javascript +const span = Sentry.startInactiveSpan({ name: 'my-span' }); +doSomething(); +span.end(); +```` + + + +See Tracing Instrumentation for more information on how to work with spans. + + + +', description: 'Attributes to add to the span.' }, + { name: 'startTime', type: 'number', description: 'The timestamp to use for the span start. If not provided, the current time will be used.' }, + { name: 'op', type: 'string', description: 'The operation name for the span. This is used to group spans in the UI' }, + { name: 'forceTransaction', type: 'boolean', description: 'If true, the span will be forced to be sent as a transaction, even if it is not the root span.' }, + { name: 'parentSpan', type: 'Span | null', description: 'The parent span for the new span. If not provided, the current span will be used.' }, + { name: 'onlyIfParent', type: 'boolean', description: 'If true, the span will only be created if there is an active span.' }, + ] + }, + }, + { name: "callback", type: "(span: Span) => T", required: true }, +]}> + Starts a new span, that is active in the provided callback. + This span will be a child of the currently active span, if there is one. + +Any spans created inside of the callback will be children of this span. + +The started span will _not_ automatically end - you have to call `span.end()` when the span is done. Please note that the span will still only be the parent span of spans created inside of the callback, while the callback is active. In most cases, you will want to use `startSpan` or `startInactiveSpan` instead. + + + ```javascript +const status = await Sentry.startSpanManual({ name: 'my-span' }, async (span) => { +const status = await doSomething(); +span.end(); +return status; +}); +``` + + +See Tracing Instrumentation for more information on how to work with spans. + + + + T", + description: "The callback to continue the trace.", + }, + ]} +> + Continues a trace in the provided callback. Any spans created inside of the + callback will be linked to the trace. + + + + Ensure that all spans created inside of the provided callback are not sent to + Sentry. + + + + Start a new trace that is active in the provided callback. + + + + Start an pageload span that will be automatically ended when the page is + considered idle. If a pageload/navigation span is currently ongoing, it will + automatically be ended first. In most cases, you do not need to call this, as + the `browserTracingIntegration` will automatically do that for you. However, + if you opt-out of pageload spans, you can use this method to manually start + such a span. Please note that this function will do nothing if + `browserTracingIntegration` has not been enabled. + + + + Start an navigation span that will be automatically ended when the page is + considered idle. If a pageload/navigation span is currently ongoing, it will + automatically be ended first. In most cases, you do not need to call this, as + the `browserTracingIntegration` will automatically do that for you. However, + if you opt-out of navigation spans, you can use this method to manually start + such a span. Please note that this function will do nothing if + `browserTracingIntegration` has not been enabled. + + +## Tracing Utilities + +These utilities can be used for more advanced tracing use cases. + + + Convert a span to a JSON object. + + + + Update the name of a span. Use this over `span.updateName(name)` to ensure + that the span is updated in all backends. + + + + Get the currently active span. + + + + Get the root span of a span. + + + + Runs the provided callback with the given span as the active span. If `null` + is provided, the callback will have no active span. + + +## Sessions + +Sessions allow you to track the release health of your application. +See the Releases & Health page for more information. + + + Starts a new session. + + + + Ends the current session (but does not send it to Sentry). + + + + Sends the current session on the scope to Sentry. Pass `true` as argument to + end the session first. + + +## Scopes + +See Scopes for more information on how to use scopes, +as well as for an explanation of the different types of scopes (current scope, isolation scope, and global scope). + + + Forks the current scope and calls the callback with the forked scope. + + + + Forks the current isolation scope and calls the callback with the forked + scope. + + + + Returns the current scope. + +Note that in most cases you should not use this API, but instead use `withScope` to generate and access a local scope. There are no guarantees about the consistency of `getCurrentScope` across different parts of your application, as scope forking may happen under the hood at various points. + + + + + Returns the current{" "} + + isolation scope + + . + + + + Returns the{" "} + + global scope + + . + + +## User Feedback + +" }, + ], + }, + description: "The feedback to capture.", + }, + { + name: "hint", + type: { + name: "Hint", + properties: [ + { + name: "captureContext", + type: { + name: "CaptureContext", + properties: [ + { + name: "user", + type: { + name: "User", + properties: [ + { name: "id", type: "string | number" }, + { name: "email", type: "string" }, + { name: "ip_address", type: "string" }, + { name: "username", type: "string" }, + ], + }, + }, + { + name: "level", + type: '"fatal" | "error" | "warning" | "log" | "info" | "debug"', + }, + { + name: "extra", + type: "Record", + description: + "Additional data that should be sent with the exception.", + }, + { + name: "tags", + type: "Record", + description: + "Additional tags that should be sent with the exception.", + }, + { + name: "contexts", + type: "Record>", + }, + { name: "fingerprint", type: "string[]" }, + ], + }, + description: + "Optional additional data to attach to the Sentry event.", + }, + ], + }, + description: + "Optional hint object containing additional information about the feedback.", + }, + ]} +> + Send user feedback to Sentry. + + + + Get the feedback integration, if it has been added. This can be used to access + the feedback integration in a type-safe way. + + +" }, + ], + }, + description: "The feedback to capture.", + }, + { + name: "hint", + type: { + name: "Hint", + properties: [ + { + name: "captureContext", + type: { + name: "CaptureContext", + properties: [ + { + name: "user", + type: { + name: "User", + properties: [ + { name: "id", type: "string | number" }, + { name: "email", type: "string" }, + { name: "ip_address", type: "string" }, + { name: "username", type: "string" }, + ], + }, + }, + { + name: "level", + type: '"fatal" | "error" | "warning" | "log" | "info" | "debug"', + }, + { + name: "extra", + type: "Record", + description: + "Additional data that should be sent with the exception.", + }, + { + name: "tags", + type: "Record", + description: + "Additional tags that should be sent with the exception.", + }, + { + name: "contexts", + type: "Record>", + }, + { name: "fingerprint", type: "string[]" }, + ], + }, + description: + "Optional additional data to attach to the Sentry event.", + }, + ], + }, + description: + "Optional hint object containing additional information about the feedback.", + }, + ]} +> + This method is similar to [`captureFeedback`](#capturefeedback), but it + returns a promise that resolves only when the feedback was successfully sent + to Sentry. It will reject if the feedback cannot be sent. + + + + +## Cron Monitoring + + + Create a cron monitor check in and send it to Sentry. + + + any, +monitorConfig?: MonitorConfig +): string`} +categorySupported={['server']} +parameters={[ +{ + name: "monitorSlug", + type: 'string', + required: true, +}, + { + name: "callback", + type: '() => any', + required: true, +}, +{ + name: "monitorConfig", + type: { + name: "MonitorConfig", + properties: [ + { + name: "schedule", + type: '{ type: "crontab", value: string } | { type: "interval", value: number, unit: "year" | "month" | "day" | "hour" | "minute" }', + required: true, + }, + { name: "checkinMargin", type: "number" }, + { name: "maxRuntime", type: "number" }, + { name: "timezone", type: "string" }, + { name: "failureIssueThreshold", type: "number" }, + { name: "recoveryThreshold", type: "number" }, + ], + }, +}, +]} +> +Wraps a callback with a cron monitor check in. The check in will be sent to Sentry when the callback finishes. + + diff --git a/docs/platforms/javascript/common/enriching-events/scopes/index.mdx b/docs/platforms/javascript/common/enriching-events/scopes/index.mdx index bc0d760424270a..d5f0f616af7c85 100644 --- a/docs/platforms/javascript/common/enriching-events/scopes/index.mdx +++ b/docs/platforms/javascript/common/enriching-events/scopes/index.mdx @@ -77,7 +77,7 @@ Sentry.withScope((scope) => { Sentry.captureException(new Error("my other error")); ``` -You can access the current scope via `Sentry.getCurrentScope()`, but usually you should use `Sentry.withScope()` to interact with local scopes instead. +You can access the current scope via `Sentry.getCurrentScope()`, but in most cases you should not use this API, but instead use `withScope` to generate and access a local scope. There are no guarantees about the consistency of `getCurrentScope` across different parts of your application, as scope forking may happen under the hood at various points. ## How Scope Data is Applied to Events diff --git a/docs/platforms/javascript/config.yml b/docs/platforms/javascript/config.yml index 4c0efe9e618baa..170bc207bf31c1 100644 --- a/docs/platforms/javascript/config.yml +++ b/docs/platforms/javascript/config.yml @@ -3,6 +3,7 @@ platformTitle: JavaScript caseStyle: camelCase supportLevel: production sdk: 'sentry.javascript.browser' +language: typescript categories: - javascript - browser diff --git a/src/components/apiExamples/apiExamples.tsx b/src/components/apiExamples/apiExamples.tsx index 8c3248bca202cb..28c92259c29c06 100644 --- a/src/components/apiExamples/apiExamples.tsx +++ b/src/components/apiExamples/apiExamples.tsx @@ -1,13 +1,7 @@ 'use client'; import {Fragment, useEffect, useState} from 'react'; -import {jsx, jsxs} from 'react/jsx-runtime'; import {Clipboard} from 'react-feather'; -import {toJsxRuntime} from 'hast-util-to-jsx-runtime'; -import {Nodes} from 'hastscript/lib/create-h'; -import bash from 'refractor/lang/bash.js'; -import json from 'refractor/lang/json.js'; -import {refractor} from 'refractor/lib/core.js'; import {type API} from 'sentry-docs/build/resolveOpenAPI'; @@ -16,9 +10,7 @@ import styles from './apiExamples.module.scss'; import {CodeBlock} from '../codeBlock'; import {CodeTabs} from '../codeTabs'; - -refractor.register(bash); -refractor.register(json); +import {codeToJsx} from '../highlightCode'; const strFormat = (str: string) => { const s = str.trim(); @@ -32,10 +24,6 @@ type Props = { api: API; }; -const codeToJsx = (code: string, lang = 'json') => { - return toJsxRuntime(refractor.highlight(code, lang) as Nodes, {Fragment, jsx, jsxs}); -}; - export function ApiExamples({api}: Props) { const apiExample = [ `curl ${api.server}${api.apiPath}`, diff --git a/src/components/callout/styles.scss b/src/components/callout/styles.scss index 312e6aa69cff2a..d1ff6e4ced9863 100644 --- a/src/components/callout/styles.scss +++ b/src/components/callout/styles.scss @@ -55,6 +55,7 @@ .callout-content { min-width: 0; + flex-grow: 1; } .callout-info { diff --git a/src/components/highlightCode.tsx b/src/components/highlightCode.tsx new file mode 100644 index 00000000000000..14e85bb8ebae9f --- /dev/null +++ b/src/components/highlightCode.tsx @@ -0,0 +1,23 @@ +import {Fragment} from 'react'; +import {jsx, jsxs} from 'react/jsx-runtime'; +import {toJsxRuntime} from 'hast-util-to-jsx-runtime'; +import {Nodes} from 'hastscript/lib/create-h'; +import bash from 'refractor/lang/bash.js'; +import json from 'refractor/lang/json.js'; +import typescript from 'refractor/lang/typescript.js'; +import {refractor} from 'refractor/lib/core.js'; + +refractor.register(bash); +refractor.register(json); +refractor.register(typescript); + +// If a new language should be supported, add it here and register it in refractor above +export const SUPPORTED_LANGUAGES = ['bash', 'json', 'typescript']; + +export function codeToJsx(code: string, lang = 'json') { + if (!SUPPORTED_LANGUAGES.includes(lang)) { + // eslint-disable-next-line no-console + console.error(`Unsupported language for syntax highlighting: ${lang}`); + } + return toJsxRuntime(refractor.highlight(code, lang) as Nodes, {Fragment, jsx, jsxs}); +} diff --git a/src/components/sdkApi.tsx b/src/components/sdkApi.tsx new file mode 100644 index 00000000000000..9272dd71e4a22a --- /dev/null +++ b/src/components/sdkApi.tsx @@ -0,0 +1,158 @@ +import {Fragment} from 'react'; + +import {getCurrentPlatform} from 'sentry-docs/docTree'; +import {serverContext} from 'sentry-docs/serverContext'; +import {PlatformCategory} from 'sentry-docs/types'; + +import {Expandable} from './expandable'; +import {codeToJsx} from './highlightCode'; +import {SdkDefinition} from './sdkDefinition'; +import {getPlatformHints} from './sdkOption'; + +export interface ParameterDef { + name: string; + type: string | ObjectParameterDef; + defaultValue?: string; + description?: string; + required?: boolean; +} + +type ObjectParameterDef = { + properties: ParameterDef[]; + name?: string; +}; + +type Props = { + name: string; + parameters: ParameterDef[]; + signature: string; + categorySupported?: PlatformCategory[]; + children?: React.ReactNode; + language?: string; +}; + +export function SdkApi({ + name, + children, + signature, + parameters = [], + language, + categorySupported = [], +}: Props) { + const {rootNode, path} = serverContext(); + const platform = getCurrentPlatform(rootNode, path); + const lang = language || platform?.language || 'typescript'; + + const {showBrowserOnly, showServerLikeOnly} = getPlatformHints(categorySupported); + + return ( + + {showBrowserOnly &&
Only available on Client
} + {showServerLikeOnly && ( +
Only available on Server
+ )} + +
{codeToJsx(signature, lang)}
+ + {parameters.length ? ( + +
+ {parameters.map(param => ( + + ))} +
+
+ ) : null} + + {children} +
+ ); +} + +function ApiParameterDef({ + name, + type, + description, + required, + language, +}: ParameterDef & {language: string}) { + return ( +
+
+ {name} + {required ? * : null} +
+
+
+ {typeof type === 'string' ? ( +
+              {codeToJsx(type, language)}
+            
+ ) : ( +
+              
+            
+ )} +
+ + {description ?

{description}

: null} +
+
+ ); +} + +function NestedObject({ + name, + objProps, + language, +}: { + language: string; + objProps: ParameterDef[]; + name?: string; +}) { + // NOTE: For now, we always render the nested object in typescript + + return ( +
+
+ + {name ? name : 'Object'} {'{'} + +
+ +
+ {objProps.map(prop => ( +
+ {prop.description && ( +
+ {codeToJsx(`// ${prop.description}`, 'typescript')} +
+ )} +
+ {typeof prop.type === 'string' ? ( + + + {prop.name} + {!prop.required ? '?' : ''}:{' '} + + {codeToJsx(prop.type, 'typescript')}, + + ) : ( + + )} +
+
+ ))} +
+
{'}'}
+
+ ); +} diff --git a/src/components/sdkDefinition/index.tsx b/src/components/sdkDefinition/index.tsx new file mode 100644 index 00000000000000..10eb1c60457d42 --- /dev/null +++ b/src/components/sdkDefinition/index.tsx @@ -0,0 +1,53 @@ +import {PlatformCategory} from 'sentry-docs/types'; + +import styles from './style.module.scss'; + +import {PlatformCategorySection} from '../platformCategorySection'; + +type Props = { + name: string; + categorySupported?: PlatformCategory[]; + children?: React.ReactNode; +}; + +export function SdkDefinition({name, children, categorySupported = []}: Props) { + return ( + +
+

+ + + + + {name} + +

+ +
{children}
+
+
+ ); +} + +export function SdkDefinitionTable({ + children, + className, +}: { + children?: React.ReactNode; + className?: string; +}) { + return ( + + {children} +
+ ); +} diff --git a/src/components/sdkOption/style.module.scss b/src/components/sdkDefinition/style.module.scss similarity index 67% rename from src/components/sdkOption/style.module.scss rename to src/components/sdkDefinition/style.module.scss index 33d94f685e53b3..20aa1816f7a093 100644 --- a/src/components/sdkOption/style.module.scss +++ b/src/components/sdkDefinition/style.module.scss @@ -1,25 +1,34 @@ -.sdk-config-option { - padding-top: 10px; +.sdk-config-option:not(:last-child) { + margin-bottom: 2rem; } .sdk-config-option-details { - padding-left: 20px; + padding-left: 0; } .sdk-option-table { --border-radius: 6px; + font-size: 1rem; margin-top: 1rem; - - width: auto !important; + width: auto; // This is necessary to make rounded borders work :( border-collapse: separate !important; border-spacing: 0; - & tr th, & tr td { + &:first-child { + margin-top: 0; + } + + &:last-child { + margin-bottom: 0; + } + + & tr th, + & tr td { border-bottom-width: 0; border-right-width: 0; - padding: .5rem .75rem + padding: 0.5rem 0.75rem; } & tr:last-child > * { @@ -46,3 +55,8 @@ border-bottom-right-radius: var(--border-radius); } } + +// This is to ensure specificity beats .prose table :( +.sdk-config-option .sdk-option-table { + width: auto; +} diff --git a/src/components/sdkOption.tsx b/src/components/sdkOption.tsx new file mode 100644 index 00000000000000..f49d94fc65092e --- /dev/null +++ b/src/components/sdkOption.tsx @@ -0,0 +1,86 @@ +import {getCurrentPlatformOrGuide} from 'sentry-docs/docTree'; +import {serverContext} from 'sentry-docs/serverContext'; +import {PlatformCategory} from 'sentry-docs/types'; + +import {PlatformCategorySection} from './platformCategorySection'; +import {PlatformSection} from './platformSection'; +import {SdkDefinition, SdkDefinitionTable} from './sdkDefinition'; + +type Props = { + name: string; + type: string; + categorySupported?: PlatformCategory[]; + children?: React.ReactNode; + defaultValue?: string; + envVar?: string; +}; + +export function SdkOption({ + name, + children, + type, + defaultValue, + envVar, + categorySupported = [], +}: Props) { + const {showBrowserOnly, showServerLikeOnly} = getPlatformHints(categorySupported); + + return ( + + + {type && } + {defaultValue && } + + + + {envVar && } + + + + {showBrowserOnly && } + {showServerLikeOnly && } + + + {children} + + ); +} + +function OptionDefRow({label, value}: {label: string; value: string}) { + return ( + + {label} + + {value} + + + ); +} + +export function getPlatformHints(categorySupported: PlatformCategory[]) { + const {rootNode, path} = serverContext(); + const currentPlatformOrGuide = getCurrentPlatformOrGuide(rootNode, path); + const currentCategories = currentPlatformOrGuide?.categories || []; + + // We only handle browser, server & serverless here for now + const currentIsBrowser = currentCategories.includes('browser'); + const currentIsServer = currentCategories.includes('server'); + const currentIsServerless = currentCategories.includes('serverless'); + const currentIsServerLike = currentIsServer || currentIsServerless; + + const hasCategorySupported = categorySupported.length > 0; + const supportedBrowserOnly = + categorySupported.includes('browser') && + !categorySupported.includes('server') && + !categorySupported.includes('serverless'); + const supportedServerLikeOnly = + !categorySupported.includes('browser') && + (categorySupported.includes('server') || categorySupported.includes('serverless')); + + const showBrowserOnly = + hasCategorySupported && supportedBrowserOnly && currentIsServerLike; + const showServerLikeOnly = + hasCategorySupported && supportedServerLikeOnly && currentIsBrowser; + + return {showBrowserOnly, showServerLikeOnly}; +} diff --git a/src/components/sdkOption/index.tsx b/src/components/sdkOption/index.tsx deleted file mode 100644 index 47156c760b40ed..00000000000000 --- a/src/components/sdkOption/index.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import {getCurrentPlatformOrGuide} from 'sentry-docs/docTree'; -import {serverContext} from 'sentry-docs/serverContext'; -import {PlatformCategory} from 'sentry-docs/types'; - -import styles from './style.module.scss'; - -import {PlatformCategorySection} from '../platformCategorySection'; -import {PlatformSection} from '../platformSection'; - -type Props = { - name: string; - type: string; - categorySupported?: PlatformCategory[]; - children?: React.ReactNode; - defaultValue?: string; - envVar?: string; - group?: string; -}; - -export function SdkOption({ - name, - children, - type, - defaultValue, - envVar, - categorySupported = [], -}: Props) { - const {showBrowserOnly, showServerLikeOnly} = getPlatformHints(categorySupported); - - return ( - -
-

- - - - - {name} - -

-
- - - {type && ( - - - - - )} - {defaultValue && ( - - - - - )} - - - {envVar && ( - - - - - )} - - - - {showBrowserOnly && ( - - - - - )} - - {showServerLikeOnly && ( - - - - - )} - -
Type - {type} -
Default - {defaultValue} -
ENV Variable - {envVar} -
Only available onClient
Only available onServer
- - {children} -
-
-
- ); -} - -function getPlatformHints(categorySupported: PlatformCategory[]) { - const {rootNode, path} = serverContext(); - const currentPlatformOrGuide = getCurrentPlatformOrGuide(rootNode, path); - const currentCategories = currentPlatformOrGuide?.categories || []; - - // We only handle browser, server & serverless here for now - const currentIsBrowser = currentCategories.includes('browser'); - const currentIsServer = currentCategories.includes('server'); - const currentIsServerless = currentCategories.includes('serverless'); - const currentIsServerLike = currentIsServer || currentIsServerless; - - const hasCategorySupported = categorySupported.length > 0; - const supportedBrowserOnly = - categorySupported.includes('browser') && - !categorySupported.includes('server') && - !categorySupported.includes('serverless'); - const supportedServerLikeOnly = - !categorySupported.includes('browser') && - (categorySupported.includes('server') || categorySupported.includes('serverless')); - - const showBrowserOnly = - hasCategorySupported && supportedBrowserOnly && currentIsServerLike; - const showServerLikeOnly = - hasCategorySupported && supportedServerLikeOnly && currentIsBrowser; - - return {showBrowserOnly, showServerLikeOnly}; -} diff --git a/src/docTree.ts b/src/docTree.ts index df69c6bca539bf..4240071e4ec45e 100644 --- a/src/docTree.ts +++ b/src/docTree.ts @@ -263,6 +263,7 @@ function nodeToPlatform(n: DocNode): Platform { caseStyle, sdk: n.frontmatter.sdk, fallbackPlatform: n.frontmatter.fallbackPlatform, + language: n.frontmatter.language, categories: n.frontmatter.categories, keywords: n.frontmatter.keywords, guides, diff --git a/src/mdxComponents.ts b/src/mdxComponents.ts index f433c793de6893..0f61fb91a36606 100644 --- a/src/mdxComponents.ts +++ b/src/mdxComponents.ts @@ -33,6 +33,7 @@ import {PlatformSdkPackageName} from './components/platformSdkPackageName'; import {PlatformSection} from './components/platformSection'; import {RelayMetrics} from './components/relayMetrics'; import {SandboxLink} from './components/sandboxLink'; +import {SdkApi} from './components/sdkApi'; import {SdkOption} from './components/sdkOption'; import {SignInNote} from './components/signInNote'; import {SmartLink} from './components/smartLink'; @@ -56,6 +57,7 @@ export function mdxComponents( CodeTabs, ConfigKey, SdkOption, + SdkApi, TableOfContents, CreateGitHubAppForm, ConfigValue, diff --git a/src/types/platform.tsx b/src/types/platform.tsx index cf6e92c0f45972..a980badcc893e6 100644 --- a/src/types/platform.tsx +++ b/src/types/platform.tsx @@ -118,6 +118,8 @@ export interface PlatformConfig { * Keywords used for search etc. */ keywords?: string[]; + /** The name of the programming language that should be used for formatting the SDK API docs. */ + language?: string; /** * The title of the platform as it should be displayed in the sidebar. * In most cases, you do not need to define this, as the title is used.