mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
[UI] Display Camelized Operation ID in API Explorer (#29785)
* updates swagger-ui to display camelized operation ids in development * attempt to fix test timing issue * fixes issue stubbing environment in swagger-ui test * adds test for operation ids in production for swagger-ui component
This commit is contained in:
@@ -8,4 +8,9 @@
|
|||||||
/* align list items with container */
|
/* align list items with container */
|
||||||
.swagger-ember .swagger-ui .wrapper {
|
.swagger-ember .swagger-ui .wrapper {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
.opblock-summary-path-description-wrapper {
|
||||||
|
width: min-content;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import { service } from '@ember/service';
|
|||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import parseURL from 'core/utils/parse-url';
|
import parseURL from 'core/utils/parse-url';
|
||||||
import config from 'open-api-explorer/config/environment';
|
import config from 'vault/config/environment';
|
||||||
|
import openApiExplorerConfig from 'open-api-explorer/config/environment';
|
||||||
import { guidFor } from '@ember/object/internals';
|
import { guidFor } from '@ember/object/internals';
|
||||||
import SwaggerUIBundle from 'swagger-ui-dist/swagger-ui-bundle.js';
|
import SwaggerUIBundle from 'swagger-ui-dist/swagger-ui-bundle.js';
|
||||||
|
import { camelize } from '@ember/string';
|
||||||
|
|
||||||
const { APP } = config;
|
const { APP } = openApiExplorerConfig;
|
||||||
|
|
||||||
export default class SwaggerUiComponent extends Component {
|
export default class SwaggerUiComponent extends Component {
|
||||||
@service auth;
|
@service auth;
|
||||||
@@ -49,16 +51,41 @@ export default class SwaggerUiComponent extends Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the operationId values in the spec are dasherized
|
||||||
|
// camelize the values so they match the function names in the generated API client SDK
|
||||||
|
CamelizeOperationIdPlugin() {
|
||||||
|
return {
|
||||||
|
wrapComponents: {
|
||||||
|
operation:
|
||||||
|
(Original, { React }) =>
|
||||||
|
(props) => {
|
||||||
|
const { operation } = props;
|
||||||
|
const operationId = operation.get('operationId');
|
||||||
|
|
||||||
|
if (operationId) {
|
||||||
|
return React.createElement(Original, {
|
||||||
|
...props,
|
||||||
|
operation: operation.set('operationId', camelize(operationId)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.createElement(Original, props);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
CONFIG = (SwaggerUIBundle, componentInstance) => {
|
CONFIG = (SwaggerUIBundle, componentInstance) => {
|
||||||
return {
|
return {
|
||||||
dom_id: `#${componentInstance.inputId}`,
|
dom_id: `#${componentInstance.inputId}`,
|
||||||
url: '/v1/sys/internal/specs/openapi',
|
url: '/v1/sys/internal/specs/openapi',
|
||||||
deepLinking: false,
|
deepLinking: false,
|
||||||
presets: [SwaggerUIBundle.presets.apis],
|
presets: [SwaggerUIBundle.presets.apis],
|
||||||
plugins: [SwaggerUIBundle.plugins.DownloadUrl, this.SearchFilterPlugin],
|
plugins: [SwaggerUIBundle.plugins.DownloadUrl, this.SearchFilterPlugin, this.CamelizeOperationIdPlugin],
|
||||||
// 'list' expands tags, but not operations
|
// 'list' expands tags, but not operations
|
||||||
docExpansion: 'list',
|
docExpansion: 'list',
|
||||||
operationsSorter: 'alpha',
|
operationsSorter: 'alpha',
|
||||||
|
displayOperationId: config.environment === 'development',
|
||||||
filter: true,
|
filter: true,
|
||||||
// this makes sure we show the x-vault- options
|
// this makes sure we show the x-vault- options
|
||||||
showExtensions: true,
|
showExtensions: true,
|
||||||
|
|||||||
@@ -4,10 +4,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Factory } from 'miragejs';
|
import { Factory } from 'miragejs';
|
||||||
/* eslint-disable ember/avoid-leaking-state-in-ember-objects */
|
|
||||||
export default Factory.extend({
|
export default Factory.extend({
|
||||||
openapi: '3.0.2',
|
openapi: '3.0.2',
|
||||||
info: {
|
// set in afterCreate to avoid leaking state lint error
|
||||||
|
info: null,
|
||||||
|
paths: null,
|
||||||
|
|
||||||
|
afterCreate(spec) {
|
||||||
|
spec.info = {
|
||||||
title: 'HashiCorp Vault API',
|
title: 'HashiCorp Vault API',
|
||||||
description: 'HTTP API that gives you full access to Vault. All API routes are prefixed with `/v1/`.',
|
description: 'HTTP API that gives you full access to Vault. All API routes are prefixed with `/v1/`.',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
@@ -15,13 +20,15 @@ export default Factory.extend({
|
|||||||
name: 'Mozilla Public License 2.0',
|
name: 'Mozilla Public License 2.0',
|
||||||
url: 'https://www.mozilla.org/en-US/MPL/2.0',
|
url: 'https://www.mozilla.org/en-US/MPL/2.0',
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
paths: {
|
|
||||||
|
spec.paths = {
|
||||||
'/auth/token/create': {
|
'/auth/token/create': {
|
||||||
description: 'The token create path is used to create new tokens.',
|
description: 'The token create path is used to create new tokens.',
|
||||||
post: {
|
post: {
|
||||||
summary: 'The token create path is used to create new tokens.',
|
summary: 'The token create path is used to create new tokens.',
|
||||||
tags: ['auth'],
|
tags: ['auth'],
|
||||||
|
operationId: 'token-create',
|
||||||
responses: {
|
responses: {
|
||||||
200: {
|
200: {
|
||||||
description: 'OK',
|
description: 'OK',
|
||||||
@@ -34,6 +41,7 @@ export default Factory.extend({
|
|||||||
post: {
|
post: {
|
||||||
summary: 'Location of a secret.',
|
summary: 'Location of a secret.',
|
||||||
tags: ['secret'],
|
tags: ['secret'],
|
||||||
|
operationId: 'kv-v2-write',
|
||||||
responses: {
|
responses: {
|
||||||
200: {
|
200: {
|
||||||
description: 'OK',
|
description: 'OK',
|
||||||
@@ -41,5 +49,6 @@ export default Factory.extend({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,50 +5,48 @@
|
|||||||
|
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupRenderingTest } from 'vault/tests/helpers';
|
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||||
import { fillIn, render, typeIn, waitFor } from '@ember/test-helpers';
|
import { fillIn, render, typeIn } from '@ember/test-helpers';
|
||||||
import { setupEngine } from 'ember-engines/test-support';
|
import { setupEngine } from 'ember-engines/test-support';
|
||||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||||
import { hbs } from 'ember-cli-htmlbars';
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
import config from 'vault/config/environment';
|
||||||
|
import { camelize } from '@ember/string';
|
||||||
|
|
||||||
const SELECTORS = {
|
const SELECTORS = {
|
||||||
container: '[data-test-swagger-ui]',
|
container: '[data-test-swagger-ui]',
|
||||||
searchInput: 'input.operation-filter-input',
|
searchInput: 'input.operation-filter-input',
|
||||||
apiPathBlock: '.opblock-post',
|
apiPathBlock: '.opblock-post',
|
||||||
|
operationId: '.opblock-summary-operation-id',
|
||||||
};
|
};
|
||||||
|
|
||||||
module('Integration | Component | open-api-explorer | swagger-ui', function (hooks) {
|
module('Integration | Component | open-api-explorer | swagger-ui', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
setupEngine(hooks, 'open-api-explorer');
|
setupEngine(hooks, 'open-api-explorer');
|
||||||
setupMirage(hooks);
|
setupMirage(hooks);
|
||||||
|
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(function () {
|
||||||
this.store = this.owner.lookup('service:store');
|
this.store = this.owner.lookup('service:store');
|
||||||
|
|
||||||
const openApiResponse = this.server.create('open-api-explorer');
|
this.openApiResponse = this.server.create('open-api-explorer');
|
||||||
this.server.get('sys/internal/specs/openapi', () => {
|
this.server.get('sys/internal/specs/openapi', () => {
|
||||||
return openApiResponse;
|
return this.openApiResponse;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.totalApiPaths = Object.keys(openApiResponse.paths).length;
|
this.totalApiPaths = Object.keys(this.openApiResponse.paths).length;
|
||||||
|
|
||||||
this.renderComponent = async () => {
|
this.renderComponent = () => render(hbs`<SwaggerUi/>`, { owner: this.engine });
|
||||||
await render(hbs`<SwaggerUi/>`, {
|
|
||||||
owner: this.engine,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`it renders`, async function (assert) {
|
test('it renders', async function (assert) {
|
||||||
await this.renderComponent();
|
await this.renderComponent();
|
||||||
|
|
||||||
await waitFor(SELECTORS.container);
|
|
||||||
|
|
||||||
assert.dom(SELECTORS.container).exists('renders component');
|
assert.dom(SELECTORS.container).exists('renders component');
|
||||||
assert.dom(SELECTORS.apiPathBlock).exists({ count: this.totalApiPaths }, 'renders all api paths');
|
assert.dom(SELECTORS.apiPathBlock).exists({ count: this.totalApiPaths }, 'renders all api paths');
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`it can search`, async function (assert) {
|
test('it can search', async function (assert) {
|
||||||
await this.renderComponent();
|
await this.renderComponent();
|
||||||
|
|
||||||
// in testing only the input is not filling correctly except after the second time
|
// in testing only the input is not filling correctly except after the second time
|
||||||
await fillIn(SELECTORS.searchInput, 'moot');
|
await fillIn(SELECTORS.searchInput, 'moot');
|
||||||
await typeIn(SELECTORS.searchInput, 'token');
|
await typeIn(SELECTORS.searchInput, 'token');
|
||||||
@@ -57,4 +55,25 @@ module('Integration | Component | open-api-explorer | swagger-ui', function (hoo
|
|||||||
// if the search fn breaks, this test will fail
|
// if the search fn breaks, this test will fail
|
||||||
assert.dom(SELECTORS.searchInput).hasValue('token', 'search input has value');
|
assert.dom(SELECTORS.searchInput).hasValue('token', 'search input has value');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it should render camelized operation ids', async function (assert) {
|
||||||
|
const envStub = sinon.stub(config, 'environment').value('development');
|
||||||
|
|
||||||
|
await this.renderComponent();
|
||||||
|
|
||||||
|
const id = this.openApiResponse.paths['/auth/token/create'].post.operationId;
|
||||||
|
assert.dom(SELECTORS.operationId).hasText(camelize(id), 'renders camelized operation id');
|
||||||
|
|
||||||
|
envStub.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should not render operation ids in production', async function (assert) {
|
||||||
|
const envStub = sinon.stub(config, 'environment').value('production');
|
||||||
|
|
||||||
|
await this.renderComponent();
|
||||||
|
|
||||||
|
assert.dom(SELECTORS.operationId).doesNotExist('operation ids are hidden in production environment');
|
||||||
|
|
||||||
|
envStub.restore();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user