diff --git a/demo/openapi.yaml b/demo/openapi.yaml index 790a76c7..558c4080 100644 --- a/demo/openapi.yaml +++ b/demo/openapi.yaml @@ -112,6 +112,9 @@ paths: summary: Add a new pet to the store description: Add new pet to the store inventory. operationId: addPet + x-badges: + - name: Global + type: global responses: '405': description: Invalid input @@ -156,6 +159,9 @@ paths: summary: Update an existing pet description: '' operationId: updatePet + x-badges: + - name: US Only + type: usonly responses: '400': description: Invalid ID supplied @@ -186,6 +192,9 @@ paths: get: description: Retrieve the history information associated with an order. operationId: GetOrderHistory + x-badges: + - name: Success + type: success parameters: - description: '' in: path @@ -244,6 +253,11 @@ paths: date range or specific query parameter (marked by * below) is provided, the order results will be limited to the last two years. operationId: GetTnOptionOrders + x-badges: + - name: 1st Badge + type: warning + - name: 2nd Badge + type: experimental parameters: - description: The status of the TN Option order being searched for. example: PROCESSING diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index e135c31e..2f743597 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -28,7 +28,15 @@ export interface OperationProps { } export const Operation = observer(({ operation }: OperationProps): JSX.Element => { - const { name: summary, description, deprecated, externalDocs, isWebhook, httpVerb } = operation; + const { + name: summary, + description, + deprecated, + badges, + externalDocs, + isWebhook, + httpVerb, + } = operation; const hasDescription = !!(description || externalDocs); const { showWebhookVerb } = React.useContext(OptionsContext); return ( @@ -45,6 +53,16 @@ export const Operation = observer(({ operation }: OperationProps): JSX.Element = Webhook {showWebhookVerb && httpVerb && '| ' + httpVerb.toUpperCase()} )} + {badges.map(badge => { + return ( + badge && ( + + {' '} + {badge.name}{' '} + + ) + ); + })} {options.pathInMiddlePanel && !isWebhook && ( diff --git a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap index e5608f39..75158076 100644 --- a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap @@ -134,6 +134,18 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "light": "#eb6d6b", "main": "#d41f1c", }, + "experimental": Object { + "contrastText": "#fff", + "dark": "#540297", + "light": "#ba68fd", + "main": "#8c03fc", + }, + "global": Object { + "contrastText": "#fff", + "dark": "#4d02b0", + "light": "#b681fd", + "main": "#7c1cfc", + }, "gray": Object { "100": "#F5F5F5", "50": "#FAFAFA", @@ -188,6 +200,12 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "secondary": "#666", }, "tonalOffset": 0.2, + "usonly": Object { + "contrastText": "#fff", + "dark": "#045b8b", + "light": "#61c4fa", + "main": "#079cee", + }, "warning": Object { "contrastText": "#ffffff", "dark": "#996300", @@ -405,6 +423,18 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "light": "#eb6d6b", "main": "#d41f1c", }, + "experimental": Object { + "contrastText": "#fff", + "dark": "#540297", + "light": "#ba68fd", + "main": "#8c03fc", + }, + "global": Object { + "contrastText": "#fff", + "dark": "#4d02b0", + "light": "#b681fd", + "main": "#7c1cfc", + }, "gray": Object { "100": "#F5F5F5", "50": "#FAFAFA", @@ -459,6 +489,12 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "secondary": "#666", }, "tonalOffset": 0.2, + "usonly": Object { + "contrastText": "#fff", + "dark": "#045b8b", + "light": "#61c4fa", + "main": "#079cee", + }, "warning": Object { "contrastText": "#ffffff", "dark": "#996300", @@ -663,6 +699,18 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "light": "#eb6d6b", "main": "#d41f1c", }, + "experimental": Object { + "contrastText": "#fff", + "dark": "#540297", + "light": "#ba68fd", + "main": "#8c03fc", + }, + "global": Object { + "contrastText": "#fff", + "dark": "#4d02b0", + "light": "#b681fd", + "main": "#7c1cfc", + }, "gray": Object { "100": "#F5F5F5", "50": "#FAFAFA", @@ -717,6 +765,12 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "secondary": "#666", }, "tonalOffset": 0.2, + "usonly": Object { + "contrastText": "#fff", + "dark": "#045b8b", + "light": "#61c4fa", + "main": "#079cee", + }, "warning": Object { "contrastText": "#ffffff", "dark": "#996300", @@ -983,6 +1037,18 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "light": "#eb6d6b", "main": "#d41f1c", }, + "experimental": Object { + "contrastText": "#fff", + "dark": "#540297", + "light": "#ba68fd", + "main": "#8c03fc", + }, + "global": Object { + "contrastText": "#fff", + "dark": "#4d02b0", + "light": "#b681fd", + "main": "#7c1cfc", + }, "gray": Object { "100": "#F5F5F5", "50": "#FAFAFA", @@ -1037,6 +1103,12 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "secondary": "#666", }, "tonalOffset": 0.2, + "usonly": Object { + "contrastText": "#fff", + "dark": "#045b8b", + "light": "#61c4fa", + "main": "#079cee", + }, "warning": Object { "contrastText": "#ffffff", "dark": "#996300", @@ -1266,6 +1338,18 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "light": "#eb6d6b", "main": "#d41f1c", }, + "experimental": Object { + "contrastText": "#fff", + "dark": "#540297", + "light": "#ba68fd", + "main": "#8c03fc", + }, + "global": Object { + "contrastText": "#fff", + "dark": "#4d02b0", + "light": "#b681fd", + "main": "#7c1cfc", + }, "gray": Object { "100": "#F5F5F5", "50": "#FAFAFA", @@ -1320,6 +1404,12 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "secondary": "#666", }, "tonalOffset": 0.2, + "usonly": Object { + "contrastText": "#fff", + "dark": "#045b8b", + "light": "#61c4fa", + "main": "#079cee", + }, "warning": Object { "contrastText": "#ffffff", "dark": "#996300", @@ -1520,6 +1610,18 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "light": "#eb6d6b", "main": "#d41f1c", }, + "experimental": Object { + "contrastText": "#fff", + "dark": "#540297", + "light": "#ba68fd", + "main": "#8c03fc", + }, + "global": Object { + "contrastText": "#fff", + "dark": "#4d02b0", + "light": "#b681fd", + "main": "#7c1cfc", + }, "gray": Object { "100": "#F5F5F5", "50": "#FAFAFA", @@ -1574,6 +1676,12 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "secondary": "#666", }, "tonalOffset": 0.2, + "usonly": Object { + "contrastText": "#fff", + "dark": "#045b8b", + "light": "#61c4fa", + "main": "#079cee", + }, "warning": Object { "contrastText": "#ffffff", "dark": "#996300", @@ -1799,6 +1907,18 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "light": "#eb6d6b", "main": "#d41f1c", }, + "experimental": Object { + "contrastText": "#fff", + "dark": "#540297", + "light": "#ba68fd", + "main": "#8c03fc", + }, + "global": Object { + "contrastText": "#fff", + "dark": "#4d02b0", + "light": "#b681fd", + "main": "#7c1cfc", + }, "gray": Object { "100": "#F5F5F5", "50": "#FAFAFA", @@ -1853,6 +1973,12 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "secondary": "#666", }, "tonalOffset": 0.2, + "usonly": Object { + "contrastText": "#fff", + "dark": "#045b8b", + "light": "#61c4fa", + "main": "#079cee", + }, "warning": Object { "contrastText": "#ffffff", "dark": "#996300", @@ -2108,6 +2234,18 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "light": "#eb6d6b", "main": "#d41f1c", }, + "experimental": Object { + "contrastText": "#fff", + "dark": "#540297", + "light": "#ba68fd", + "main": "#8c03fc", + }, + "global": Object { + "contrastText": "#fff", + "dark": "#4d02b0", + "light": "#b681fd", + "main": "#7c1cfc", + }, "gray": Object { "100": "#F5F5F5", "50": "#FAFAFA", @@ -2162,6 +2300,12 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "secondary": "#666", }, "tonalOffset": 0.2, + "usonly": Object { + "contrastText": "#fff", + "dark": "#045b8b", + "light": "#61c4fa", + "main": "#079cee", + }, "warning": Object { "contrastText": "#ffffff", "dark": "#996300", @@ -2379,6 +2523,18 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "light": "#eb6d6b", "main": "#d41f1c", }, + "experimental": Object { + "contrastText": "#fff", + "dark": "#540297", + "light": "#ba68fd", + "main": "#8c03fc", + }, + "global": Object { + "contrastText": "#fff", + "dark": "#4d02b0", + "light": "#b681fd", + "main": "#7c1cfc", + }, "gray": Object { "100": "#F5F5F5", "50": "#FAFAFA", @@ -2433,6 +2589,12 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "secondary": "#666", }, "tonalOffset": 0.2, + "usonly": Object { + "contrastText": "#fff", + "dark": "#045b8b", + "light": "#61c4fa", + "main": "#079cee", + }, "warning": Object { "contrastText": "#ffffff", "dark": "#996300", @@ -2637,6 +2799,18 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "light": "#eb6d6b", "main": "#d41f1c", }, + "experimental": Object { + "contrastText": "#fff", + "dark": "#540297", + "light": "#ba68fd", + "main": "#8c03fc", + }, + "global": Object { + "contrastText": "#fff", + "dark": "#4d02b0", + "light": "#b681fd", + "main": "#7c1cfc", + }, "gray": Object { "100": "#F5F5F5", "50": "#FAFAFA", @@ -2691,6 +2865,12 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "secondary": "#666", }, "tonalOffset": 0.2, + "usonly": Object { + "contrastText": "#fff", + "dark": "#045b8b", + "light": "#61c4fa", + "main": "#079cee", + }, "warning": Object { "contrastText": "#ffffff", "dark": "#996300", diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index e26b4a4c..96fbb43a 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -20,7 +20,12 @@ import { RequestBodyModel } from './RequestBody'; import { ResponseModel } from './Response'; import { SideNavStyleEnum } from '../types'; -import type { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types'; +import type { + OpenAPIExternalDocumentation, + OpenAPIServer, + OpenAPIXCodeSample, + OperationCustomBadge, +} from '../../types'; import type { OpenAPIParser } from '../OpenAPIParser'; import type { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import type { MediaContentModel } from './MediaContent'; @@ -72,6 +77,7 @@ export class OperationModel implements IMenuItem { operationHash?: string; httpVerb: string; deprecated: boolean; + badges: OperationCustomBadge[]; path: string; servers: OpenAPIServer[]; security: SecurityRequirementModel[]; @@ -96,6 +102,7 @@ export class OperationModel implements IMenuItem { this.externalDocs = operationSpec.externalDocs; this.deprecated = !!operationSpec.deprecated; + this.badges = operationSpec['x-badges'] ? operationSpec['x-badges'] : []; this.httpVerb = operationSpec.httpVerb; this.deprecated = !!operationSpec.deprecated; this.operationId = operationSpec.operationId; diff --git a/src/theme.ts b/src/theme.ts index e11209b6..4fca8a65 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -37,6 +37,24 @@ const defaultTheme: ThemeInterface = { dark: ({ colors }) => darken(colors.tonalOffset, colors.error.main), contrastText: ({ colors }) => readableColor(colors.error.main), }, + global: { + main: '#7c1cfc', + light: ({ colors }) => lighten(colors.tonalOffset, colors.global.main), + dark: ({ colors }) => darken(colors.tonalOffset, colors.global.main), + contrastText: ({ colors }) => readableColor(colors.global.main), + }, + usonly: { + main: '#079cee', + light: ({ colors }) => lighten(colors.tonalOffset, colors.usonly.main), + dark: ({ colors }) => darken(colors.tonalOffset, colors.usonly.main), + contrastText: ({ colors }) => readableColor(colors.global.main), + }, + experimental: { + main: '#8c03fc', + light: ({ colors }) => lighten(colors.tonalOffset, colors.experimental.main), + dark: ({ colors }) => darken(colors.tonalOffset, colors.experimental.main), + contrastText: ({ colors }) => readableColor(colors.experimental.main), + }, gray: { 50: '#FAFAFA', 100: '#F5F5F5', @@ -267,6 +285,9 @@ export interface ResolvedThemeInterface { success: ColorSetting; warning: ColorSetting; error: ColorSetting; + global: ColorSetting; + usonly: ColorSetting; + experimental: ColorSetting; gray: { 50: string; 100: string; diff --git a/src/types/open-api.ts b/src/types/open-api.ts index fd80bf8d..dab3729c 100644 --- a/src/types/open-api.ts +++ b/src/types/open-api.ts @@ -64,6 +64,20 @@ export interface OpenAPIPath { $ref?: string; } +export type OperationCustomBadgeType = + | 'primary' + | 'success' + | 'warning' + | 'error' + | 'global' + | 'usonly' + | 'experimental'; + +export interface OperationCustomBadge { + name: string; + type: OperationCustomBadgeType; +} + export interface OpenAPIXCodeSample { lang: string; label?: string; @@ -83,6 +97,7 @@ export interface OpenAPIOperation { deprecated?: boolean; security?: OpenAPISecurityRequirement[]; servers?: OpenAPIServer[]; + 'x-badges'?: OperationCustomBadge[]; 'x-codeSamples'?: OpenAPIXCodeSample[]; 'x-code-samples'?: OpenAPIXCodeSample[]; // deprecated } diff --git a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap index c1618ad3..6f3fc77e 100644 --- a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap +++ b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap @@ -773,6 +773,16 @@ and standard method from web, mobile and desktop applications. "tags": Array [ "Tn Option", ], + "x-badges": Array [ + Object { + "name": "1st Badge", + "type": "warning", + }, + Object { + "name": "2nd Badge", + "type": "experimental", + }, + ], }, "post": Object { "description": "Create TN Option order to assign line features to the telephone number.

Attribute description:", @@ -1095,6 +1105,12 @@ and standard method from web, mobile and desktop applications. "tags": Array [ "Tn Option", ], + "x-badges": Array [ + Object { + "name": "Success", + "type": "success", + }, + ], }, }, "/pet": Object { @@ -1144,6 +1160,12 @@ and standard method from web, mobile and desktop applications. "tags": Array [ "pet", ], + "x-badges": Array [ + Object { + "name": "Global", + "type": "global", + }, + ], "x-codeSamples": Array [ Object { "lang": "C#", @@ -1208,6 +1230,12 @@ try { "tags": Array [ "pet", ], + "x-badges": Array [ + Object { + "name": "US Only", + "type": "usonly", + }, + ], "x-codeSamples": Array [ Object { "lang": "PHP", diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 22424e90..561811c7 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -662,6 +662,7 @@ export function isRedocExtension(key: string): boolean { 'x-traitTag': true, 'x-additionalPropertiesName': true, 'x-explicitMappingOnly': true, + 'x-badges': true, }; return key in redocExtensions;