1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-18 13:48:58 +02:00

feat: clean up old OAS and ability to add badges to descriptions (#10038)

## About the changes
This removes some old and unused files from an initial test of setting
up openapi that's currently not working:

![image](https://github.com/user-attachments/assets/5ed9b1bf-a3b1-48ec-9232-6e534e6ceb96)


Also, adds the ability of marking endpoints as (note, the endpoints are
made up just for illustration purposes):
- Enterprise only 

![image](https://github.com/user-attachments/assets/0f8768b3-051f-4dc5-89d9-f09b38d5ca4c)

- Beta: 

![image](https://github.com/user-attachments/assets/45a29b16-6063-4718-bda6-e6ac28061be5)

- Both: 

![image](https://github.com/user-attachments/assets/2ed8bc95-868b-4577-8f84-67b74e739a51)

---------

Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
Gastón Fournier 2025-05-28 13:14:00 +02:00 committed by GitHub
parent 1f4aa0ed34
commit ff83f934d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 45 additions and 123 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 B

View File

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>ReDoc</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<!--
ReDoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<!-- ### DEFAULT REDOC - NO 'TRY' FEATURE -->
<!-- <redoc spec-url='openapi.yaml'></redoc> -->
<!-- <script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script> -->
<!-- ### REDOC WITH SWAGGER UI'S 'TRY' FEATURE WITHIN REDOC -->
<!-- From https://github.com/wll8/redoc-try -->
<div id="redoc-container"></div>
<script src="//cdn.jsdelivr.net/npm/redoc@2.0.0-rc.28/bundles/redoc.standalone.min.js"> </script>
<script src="//cdn.jsdelivr.net/gh/wll8/redoc-try/dist/try.js"></script>
<script>
var unleashRedoc = {
openApi: `openapi.yaml`,
onlySwagger: false, // Set to true to switch to Swagger UI output
tryText: `Try it out`, // Try button text
trySwaggerInApi: false, // Do not display Swagger debugging window under API
redocOptions: {hideDownloadButton: true, noAutoAuth: true, sortPropsAlphabetically: true, theme: {spacing: {sectionVertical: '5px',}},},
swaggerOptions: {dom_id: `#swagger-ui`},
}
initTry(unleashRedoc);
</script>
</body>
</html>

View File

@ -1,74 +0,0 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
window.addEventListener('DOMContentLoaded', function () {
run();
});
</script>

1
openapi-static/Beta.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="35" height="20" role="img" aria-label="Beta"><title>Beta</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="35" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="20" fill="#6c65e5"/><rect x="0" width="35" height="20" fill="#6c65e5"/><rect width="35" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="175" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">Beta</text><text x="175" y="140" transform="scale(.1)" fill="#fff" textLength="250">Beta</text></g></svg>

After

Width:  |  Height:  |  Size: 860 B

View File

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M11.2608 0.811331C11.6574 0.375976 12.3426 0.375977 12.7392 0.811332L14.0529 2.25322C14.316 2.54202 14.7233 2.65114 15.0955 2.53259L16.9542 1.94073C17.5154 1.76203 18.1087 2.10458 18.2345 2.67993L18.6513 4.58549C18.7347 4.96716 19.0328 5.26527 19.4145 5.34875L21.3201 5.7655C21.8954 5.89133 22.238 6.48465 22.0593 7.04583L21.4674 8.90447C21.3489 9.27675 21.458 9.68397 21.7468 9.9471L23.1887 11.2608C23.624 11.6574 23.624 12.3426 23.1887 12.7392L21.7468 14.0529C21.458 14.316 21.3489 14.7233 21.4674 15.0955L22.0593 16.9542C22.238 17.5154 21.8954 18.1087 21.3201 18.2345L19.4145 18.6513C19.0328 18.7347 18.7347 19.0328 18.6513 19.4145L18.2345 21.3201C18.1087 21.8954 17.5154 22.238 16.9542 22.0593L15.0955 21.4674C14.7233 21.3489 14.316 21.458 14.0529 21.7468L12.7392 23.1887C12.3426 23.624 11.6574 23.624 11.2608 23.1887L9.9471 21.7468C9.68397 21.458 9.27675 21.3489 8.90447 21.4674L7.04583 22.0593C6.48465 22.238 5.89133 21.8954 5.7655 21.3201L5.34875 19.4145C5.26527 19.0328 4.96716 18.7347 4.58549 18.6513L2.67993 18.2345C2.10458 18.1087 1.76203 17.5154 1.94073 16.9542L2.53259 15.0955C2.65114 14.7233 2.54202 14.316 2.25322 14.0529L0.811331 12.7392C0.375976 12.3426 0.375977 11.6574 0.811332 11.2608L2.25322 9.9471C2.54202 9.68397 2.65114 9.27675 2.53259 8.90447L1.94073 7.04583C1.76203 6.48465 2.10458 5.89133 2.67993 5.7655L4.58549 5.34875C4.96716 5.26527 5.26527 4.96716 5.34875 4.58549L5.7655 2.67993C5.89133 2.10458 6.48465 1.76203 7.04583 1.94073L8.90447 2.53259C9.27675 2.65114 9.68397 2.54202 9.9471 2.25322L11.2608 0.811331Z"
fill="#1A4049" />
<path d="M10.4351 7.3042H7.30469V16.6955H16.696V7.3042H13.5656V13.5651H10.4351V7.3042Z" fill="white" />
<path d="M13.5664 13.5649H16.6968V16.6954H13.5664V13.5649Z" fill="#817AFE" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -100,10 +100,6 @@ export default async function getApp(
app.use(baseUriPath, favicon(path.join(publicFolder, 'favicon.ico')));
app.use(baseUriPath, express.static(publicFolder, { index: false }));
if (config.enableOAS) {
app.use(`${baseUriPath}/oas`, express.static('docs/api/oas'));
}
if (config.enableOAS && services.openApiService) {
services.openApiService.useDocs(app);
}

View File

@ -14,4 +14,6 @@ export interface ApiOperation<Tag = OpenApiTag | DeprecatedOpenAPITag>
extends Omit<OpenAPIV3.OperationObject, 'tags'> {
operationId: string;
tags: [Tag];
beta?: boolean;
enterpriseOnly?: boolean;
}

View File

@ -1,5 +1,10 @@
import openapi, { type IExpressOpenApi } from '@wesleytodd/openapi';
import type { Express, RequestHandler, Response } from 'express';
import {
type Express,
type RequestHandler,
type Response,
static as expressStatic,
} from 'express';
import type { IUnleashConfig } from '../types/option.js';
import {
createOpenApiSchema,
@ -38,12 +43,40 @@ export class OpenApiService {
}
validPath(op: ApiOperation): RequestHandler {
return this.api.validPath(op);
const { beta, enterpriseOnly, ...rest } = op;
const betaBadge = beta
? `![Beta](${this.docsStaticsPath()}/Beta.svg) This is a beta endpoint and it may change or be removed in the future.
`
: '';
const enterpriseBadge = enterpriseOnly
? `![Unleash Enterprise](${this.docsStaticsPath()}/Enterprise.svg) **Enterprise feature**
`
: '';
return this.api.validPath({
...rest,
description:
`${enterpriseBadge}${betaBadge}${op.description}`.replaceAll(
/\n\s*/g,
'\n\n',
),
});
}
useDocs(app: Express): void {
app.use(this.api);
app.use(this.docsPath(), this.api.swaggerui());
app.use(
this.docsStaticsPath(),
expressStatic('openapi-static', { index: false }),
);
}
docsStaticsPath(): string {
const { baseUriPath = '' } = this.config.server ?? {};
return `${baseUriPath}/docs/static`;
}
docsPath(): string {