Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ demo/vendor.js
# AMF models
/demo/*.json
!demo/apis.json
!demo/grpc-test.json
!demo/grpc-test-compact.json

.idea/
.codegenie
4,490 changes: 4,490 additions & 0 deletions demo/grpc-test-compact.json

Large diffs are not rendered by default.

4,490 changes: 4,490 additions & 0 deletions demo/grpc-test.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@api-components/api-documentation",
"description": "A main documentation view for AMF model",
"version": "6.1.7",
"version": "6.1.8",
"license": "Apache-2.0",
"main": "index.js",
"module": "index.js",
Expand Down
25 changes: 23 additions & 2 deletions src/ApiDocumentationElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,27 @@ export class ApiDocumentationElement extends EventsTargetMixin(AmfHelperMixin(Li
return baseUri || serverValue;
}

/**
* Computes whether the "Try It" button should be hidden.
* Returns true if noTryIt is explicitly set or if the current operation is gRPC.
* @returns {boolean}
*/
get effectiveNoTryIt() {
const { noTryIt, _docsModel, selectedType } = this;

// If noTryIt is explicitly set, respect that
if (noTryIt) {
return true;
}

// If we're viewing a method and it's a gRPC operation, hide Try It
if (selectedType === 'method' && _docsModel) {
return this._isGrpcOperation(_docsModel);
}

return false;
}

get inlineMethods() {
return this._inlineMethods;
}
Expand Down Expand Up @@ -1181,7 +1202,7 @@ export class ApiDocumentationElement extends EventsTargetMixin(AmfHelperMixin(Li
}

_methodTemplate() {
const { amf, _docsModel, narrow, compatibility, _endpoint, selected, noTryIt, graph, noBottomNavigation, server } = this;
const { amf, _docsModel, narrow, compatibility, _endpoint, selected, graph, noBottomNavigation, server } = this;
const prev = this._computeMethodPrevious(amf, selected);
const next = this._computeMethodNext(amf, selected);

Expand All @@ -1195,7 +1216,7 @@ export class ApiDocumentationElement extends EventsTargetMixin(AmfHelperMixin(Li
.previous="${prev}"
.next="${next}"
.baseUri="${this.effectiveBaseUri}"
?noTryIt="${noTryIt}"
?noTryIt="${this.effectiveNoTryIt}"
?graph="${graph}"
?noNavigation="${noBottomNavigation}"
renderSecurity
Expand Down
31 changes: 31 additions & 0 deletions test/amf-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,34 @@ AmfLoader.lookupEncodes = (model) => {
const key = helper._getAmfKey(helper.ns.aml.vocabularies.document.encodes);
return helper._ensureArray(model[key]);
};

// gRPC-specific helpers
AmfLoader.lookupGrpcService = (model, serviceName) => {
helper.amf = model;
const webApi = helper._computeApi(model);
const eKey = helper._getAmfKey(helper.ns.aml.vocabularies.apiContract.endpoint);
const endpoints = helper._ensureArray(webApi[eKey]);
if (!endpoints) {
return undefined;
}
return endpoints.find((endpoint) => {
const name = helper._getValue(endpoint, helper.ns.aml.vocabularies.core.name);
return name === serviceName;
});
};

AmfLoader.lookupGrpcMethod = (model, serviceName, methodName) => {
const service = AmfLoader.lookupGrpcService(model, serviceName);
if (!service) {
return undefined;
}
const opKey = helper._getAmfKey(helper.ns.aml.vocabularies.apiContract.supportedOperation);
const ops = helper._ensureArray(service[opKey]);
if (!ops) {
return undefined;
}
return ops.find((op) => {
const name = helper._getValue(op, helper.ns.aml.vocabularies.core.name);
return name === methodName;
});
};
94 changes: 94 additions & 0 deletions test/api-documentation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1146,4 +1146,98 @@ describe('ApiDocumentationElement', () => {
assert.isDefined(element._docsModel);
});
});

[
['Compact model', true],
['Full model', false]
].forEach(([label, compact]) => {
describe(`gRPC API - ${label}`, () => {
const grpcApi = 'grpc-test';
let amf;

before(async () => {
amf = await AmfLoader.load(grpcApi, Boolean(compact));
});

describe('effectiveNoTryIt getter', () => {
it('returns true for gRPC operations', async () => {
// Find gRPC service and method
const service = AmfLoader.lookupGrpcService(amf, 'Greeter');
const method = AmfLoader.lookupGrpcMethod(amf, 'Greeter', 'SayHello1');

assert.ok(service, 'gRPC service should be found');
assert.ok(method, 'gRPC method should be found');

const element = await modelFixture(amf, 'method', method['@id']);
element.endpointId = service['@id'];
await aTimeout(0);

// Verify effectiveNoTryIt returns true for gRPC operation
assert.isTrue(element.effectiveNoTryIt, 'effectiveNoTryIt should be true for gRPC operations');
});

it('respects explicit noTryIt property', async () => {
const service = AmfLoader.lookupGrpcService(amf, 'Greeter');
const method = AmfLoader.lookupGrpcMethod(amf, 'Greeter', 'SayHello1');

const element = await modelFixture(amf, 'method', method['@id']);
element.endpointId = service['@id'];
element.noTryIt = true;
await aTimeout(0);

assert.isTrue(element.effectiveNoTryIt, 'effectiveNoTryIt should respect explicit noTryIt');
});
});

describe('renders gRPC method with noTryIt', () => {
it('hides Try It button for gRPC operations', async () => {
const service = AmfLoader.lookupGrpcService(amf, 'Greeter');
const method = AmfLoader.lookupGrpcMethod(amf, 'Greeter', 'SayHello1');

assert.ok(service, 'gRPC service should be found');
assert.ok(method, 'gRPC method should be found');

const element = await modelFixture(amf, 'method', method['@id']);
element.endpointId = service['@id'];
element.narrow = true;
element.compatibility = true;
await aTimeout(0);

const node = /** @type any */ (element.shadowRoot.querySelector('api-method-documentation'));
assert.ok(node, 'method is rendered');
assert.typeOf(node.amf, 'array', 'amf is set');
assert.equal(node.endpoint['@id'], service['@id'], 'endpoint model is set');
assert.equal(node.method['@id'], method['@id'], 'method model is set');

// This is the critical assertion - noTryIt should be true for gRPC
assert.isTrue(node.noTryIt, 'noTryIt should be true for gRPC operations');

// Additional verifications
assert.equal(node.narrow, element.narrow, 'narrow is set');
assert.equal(node.compatibility, element.compatibility, 'compatibility is set');
});

it('fails if Try It button is shown for gRPC operations', async () => {
const service = AmfLoader.lookupGrpcService(amf, 'Greeter');
const method = AmfLoader.lookupGrpcMethod(amf, 'Greeter', 'SayHello1');

assert.ok(service, 'gRPC service should be found');
assert.ok(method, 'gRPC method should be found');

const element = await modelFixture(amf, 'method', method['@id']);
element.endpointId = service['@id'];
await aTimeout(0);

const node = /** @type any */ (element.shadowRoot.querySelector('api-method-documentation'));

// This test will fail if noTryIt is false (i.e., if the button is shown)
if (!node.noTryIt) {
throw new Error('FAIL: Try It button is shown for gRPC operation when it should be hidden');
}

assert.isTrue(node.noTryIt, 'noTryIt must be true - Try It button should NOT be shown for gRPC');
});
});
});
});
});