Skip to content

Commit f23dbb6

Browse files
committed
multiple tags
1 parent 53582ed commit f23dbb6

File tree

5 files changed

+217
-16
lines changed

5 files changed

+217
-16
lines changed

src/N8NPropertiesBuilder.spec.ts

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,3 +742,199 @@ test('test overrides', () => {
742742
},
743743
]);
744744
});
745+
746+
test('multiple tags', () => {
747+
const paths = {
748+
'/api/entities': {
749+
get: {
750+
operationId: 'EntityController_list',
751+
summary: 'List all entities',
752+
parameters: [
753+
{
754+
name: 'all',
755+
required: false,
756+
in: 'query',
757+
example: false,
758+
description: 'Boolean flag description',
759+
schema: {
760+
type: 'boolean',
761+
},
762+
},
763+
],
764+
tags: [
765+
'🖥️ Entity',
766+
"Another Tag"
767+
],
768+
},
769+
},
770+
};
771+
772+
const parser = new N8NPropertiesBuilder({paths});
773+
const result = parser.build()
774+
775+
expect(result).toEqual(
776+
[
777+
{
778+
"default": "",
779+
"displayName": "Resource",
780+
"name": "resource",
781+
"noDataExpression": true,
782+
"options": [
783+
{
784+
"description": "",
785+
"name": "🖥️ Entity",
786+
"value": "Entity"
787+
},
788+
{
789+
"description": "",
790+
"name": "Another Tag",
791+
"value": "Another Tag"
792+
}
793+
],
794+
"type": "options"
795+
},
796+
{
797+
"default": "",
798+
"displayName": "Operation",
799+
"displayOptions": {
800+
"show": {
801+
"resource": [
802+
"Entity"
803+
]
804+
}
805+
},
806+
"name": "operation",
807+
"noDataExpression": true,
808+
"options": [
809+
{
810+
"action": "List all entities",
811+
"description": "List all entities",
812+
"name": "List",
813+
"routing": {
814+
"request": {
815+
"method": "GET",
816+
"url": "=/api/entities"
817+
}
818+
},
819+
"value": "List"
820+
}
821+
],
822+
"type": "options"
823+
},
824+
{
825+
"default": "",
826+
"displayName": "Operation",
827+
"displayOptions": {
828+
"show": {
829+
"resource": [
830+
"Another Tag"
831+
]
832+
}
833+
},
834+
"name": "operation",
835+
"noDataExpression": true,
836+
"options": [
837+
{
838+
"action": "List all entities",
839+
"description": "List all entities",
840+
"name": "List",
841+
"routing": {
842+
"request": {
843+
"method": "GET",
844+
"url": "=/api/entities"
845+
}
846+
},
847+
"value": "List"
848+
}
849+
],
850+
"type": "options"
851+
},
852+
{
853+
"default": "",
854+
"displayName": "GET /api/entities",
855+
"displayOptions": {
856+
"show": {
857+
"operation": [
858+
"List"
859+
],
860+
"resource": [
861+
"Entity"
862+
]
863+
}
864+
},
865+
"name": "operation",
866+
"type": "notice",
867+
"typeOptions": {
868+
"theme": "info"
869+
}
870+
},
871+
{
872+
"default": false,
873+
"description": "Boolean flag description",
874+
"displayName": "All",
875+
"displayOptions": {
876+
"show": {
877+
"operation": [
878+
"List"
879+
],
880+
"resource": [
881+
"Entity"
882+
]
883+
}
884+
},
885+
"name": "all",
886+
"routing": {
887+
"request": {
888+
"qs": {
889+
"all": "={{ $value }}"
890+
}
891+
}
892+
},
893+
"type": "boolean"
894+
},
895+
{
896+
"default": "",
897+
"displayName": "GET /api/entities",
898+
"displayOptions": {
899+
"show": {
900+
"operation": [
901+
"List"
902+
],
903+
"resource": [
904+
"Another Tag"
905+
]
906+
}
907+
},
908+
"name": "operation",
909+
"type": "notice",
910+
"typeOptions": {
911+
"theme": "info"
912+
}
913+
},
914+
{
915+
"default": false,
916+
"description": "Boolean flag description",
917+
"displayName": "All",
918+
"displayOptions": {
919+
"show": {
920+
"operation": [
921+
"List"
922+
],
923+
"resource": [
924+
"Another Tag"
925+
]
926+
}
927+
},
928+
"name": "all",
929+
"routing": {
930+
"request": {
931+
"qs": {
932+
"all": "={{ $value }}"
933+
}
934+
}
935+
},
936+
"type": "boolean"
937+
}
938+
]
939+
);
940+
});

src/OperationParser.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {OperationContext} from "./openapi/OpenAPIVisitor";
44
import {toResourceName} from "./n8n/utils";
55

66
export interface IOperationParser {
7-
getResourceName(operation: OpenAPIV3.OperationObject, context: OperationContext): string
7+
getResources(operation: OpenAPIV3.OperationObject, context: OperationContext): string[]
88

99
getOperationName(operation: OpenAPIV3.OperationObject, context: OperationContext): string
1010

@@ -14,12 +14,13 @@ export interface IOperationParser {
1414
}
1515

1616
export class N8NOperationParser implements IOperationParser {
17-
getResourceName(operation: OpenAPIV3.OperationObject, context: OperationContext): string {
17+
getResources(operation: OpenAPIV3.OperationObject, context: OperationContext): string[] {
1818
const tags = operation.tags;
1919
if (!tags || tags.length === 0) {
20+
// TODO: Add "default" tag
2021
throw new Error(`No tags found for operation '${operation}'`);
2122
}
22-
return toResourceName(tags[0]);
23+
return tags.map(toResourceName)
2324
}
2425

2526
getOperationName(operation: OpenAPIV3.OperationObject, context: OperationContext): string {

src/OperationsCollector.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {OpenAPIVisitor, OperationContext} from "./openapi/OpenAPIVisitor";
2+
import * as lodash from "lodash";
23
import pino from "pino";
34
import {OpenAPIV3} from "openapi-types";
45
import {N8NINodeProperties} from "./SchemaToINodeProperties";
@@ -76,12 +77,15 @@ export class BaseOperationsCollector implements OpenAPIVisitor {
7677
if (!tags || tags.length === 0) {
7778
throw new Error(`No tags found for operation '${operation}'`);
7879
}
79-
const {option, fields} = this.parseOperation(operation, context);
80-
const resourceName = this.operationParser.getResourceName(operation, context);
81-
const operationName = option.name;
82-
this.addDisplayOption(fields, resourceName, operationName)
83-
this.optionsByResource.add(resourceName, option);
84-
this._fields.push(...fields)
80+
const {option, fields: operationFields} = this.parseOperation(operation, context);
81+
const resources = this.operationParser.getResources(operation, context);
82+
for (const resourceName of resources) {
83+
const fields = lodash.cloneDeep(operationFields)
84+
const operationName = option.name;
85+
this.addDisplayOption(fields, resourceName, operationName)
86+
this.optionsByResource.add(resourceName, option);
87+
this._fields.push(...fields)
88+
}
8589
}
8690

8791
parseFields(operation: OpenAPIV3.OperationObject, context: OperationContext) {

src/ResourcePropertiesCollector.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface TagObject {
1111

1212
/**
1313
* Collects resource properties from OpenAPI document
14+
* Resource is basically tags from OpenAPI spec
1415
*/
1516
export class ResourcePropertiesCollector implements OpenAPIVisitor {
1617
private tags: Map<string, TagObject>;
@@ -21,9 +22,6 @@ export class ResourcePropertiesCollector implements OpenAPIVisitor {
2122
}
2223

2324
get iNodeProperty(): INodeProperties {
24-
if (this.tags.size === 0) {
25-
throw new Error('No tags found in OpenAPI document')
26-
}
2725
const tags = this.sortedTags
2826
const options = tags.map((tag) => {
2927
return {
@@ -51,13 +49,15 @@ export class ResourcePropertiesCollector implements OpenAPIVisitor {
5149
}
5250

5351
visitOperation(operation: OpenAPIV3.OperationObject, context: OperationContext) {
54-
const tags = operation.tags;
52+
let tags = operation.tags
5553
if (!tags || tags.length === 0) {
54+
// TODO: add 'default' at the end
5655
return;
5756
}
58-
// get first tag
59-
const tag = tags[0];
57+
tags.forEach((tag) => this.addTagByName(tag))
58+
}
6059

60+
private addTagByName(tag: string) {
6161
// insert if not found
6262
if (!this.tags.has(tag)) {
6363
this.tags.set(tag, {

src/n8n/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as lodash from "lodash";
66
*/
77
export function toResourceName(name: string) {
88
// keep only ascii, no emojis
9-
return lodash.startCase(name.replace(/[^a-zA-Z0-9]/g, ''))
9+
return lodash.startCase(name.replace(/[^a-zA-Z0-9_-]/g, ''));
1010
}
1111

1212
/**

0 commit comments

Comments
 (0)