diff --git a/src/cli.ts b/src/cli.ts index 32749c8f..692ff609 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -85,15 +85,18 @@ commander }); commander - .command('mapping [ignoredTags]') - .action(async (relativeSrcPath, relativeMappingPath, ignoredTags) => { + .command('mapping ') + .option('-m, --mappingPath ', 'Mapping Path') + .option('-i, --ignoredTags ', 'Ignored Tags') + .option('-a, --aggPath ', 'Aggregations Path') + .option('-e, --errorPath ', 'Error Path') + .action(async (relativeSrcPath, options) => { // get absolute paths const srcPath = resolve(relativeSrcPath); - const mappingPath = resolve(relativeMappingPath); let ignoredTagsList: string[] = []; - if (typeof ignoredTags === 'string') { - ignoredTagsList = ignoredTags.split(','); + if (typeof options.ignoredTags === 'string') { + ignoredTagsList = options.ignoredTags.split(','); } // get project reflection @@ -107,10 +110,24 @@ commander } // write documentation to file - // tslint:disable-next-line:no-magic-numbers - writeFileSync(mappingPath, JSON.stringify(result.mappings, null, 2)); - - Logger.ok(`Elasticsearch mapping written to ${mappingPath}.`); + if (typeof options.aggPath !== 'undefined') { + const aggPath = resolve(options.aggPath); + // tslint:disable-next-line:no-magic-numbers + writeFileSync(aggPath, JSON.stringify(result.aggregations, null, 2)); + Logger.ok(`Elasticsearch aggregations written to ${aggPath}.`); + } + if (typeof options.mappingPath !== 'undefined') { + const mappingPath = resolve(options.mappingPath); + // tslint:disable-next-line:no-magic-numbers + writeFileSync(mappingPath, JSON.stringify(result.mappings, null, 2)); + Logger.ok(`Elasticsearch mappings written to ${mappingPath}.`); + } + if (typeof options.errorPath !== 'undefined') { + const errPath = resolve(options.errorPath); + // tslint:disable-next-line:no-magic-numbers + writeFileSync(errPath, JSON.stringify(result.errors, null, 2)); + Logger.ok(`Mapping errors written to ${errPath}.`); + } }); commander diff --git a/src/mapping.ts b/src/mapping.ts index 97f67573..4b737505 100644 --- a/src/mapping.ts +++ b/src/mapping.ts @@ -15,7 +15,7 @@ import {Logger} from '@openstapps/logger'; import merge from 'deepmerge'; import {stringify} from 'flatted'; -import {DeclarationReflection, ProjectReflection} from 'typedoc'; +import {DeclarationReflection, ProjectReflection, SignatureReflection} from 'typedoc'; import { ArrayType, Comment, @@ -35,7 +35,8 @@ import {settings} from './mappings/definitions/settings'; import {dynamicTypes, ElasticsearchDataType, typemap} from './mappings/definitions/typemap'; import { ElasticsearchDynamicTemplate, - ElasticsearchObject, ElasticsearchTemplateCollection, + ElasticsearchObject, + ElasticsearchTemplateCollection, ElasticsearchType, ElasticsearchValue, } from './mappings/mapping-definitions'; @@ -116,6 +117,7 @@ function composeErrorMessage(path: string, topTypeName: string, typeName: string * @param type the ReferenceType of a DeclarationReflection * @param out the previous reflection, it then overrides all parameters or keeps old ones * @param path the current path to the object we are in + * @param topTypeName the type field value * @param tags any tags attached to the type */ function getReflectionGeneric(type: ReferenceType, @@ -198,6 +200,7 @@ function handleExternalType(ref: ReferenceType, generics: Map, @@ -221,14 +224,17 @@ function handleDeclarationReflection(decl: DeclarationReflection, let empty = true; // first check if there are any index signatures, so for example `[name: string]: Foo` - if (typeof decl.indexSignature !== 'undefined' && typeof decl.indexSignature.parameters !== 'undefined') { + if (typeof decl.indexSignature !== 'undefined') { out.dynamic = true; - for (const param of decl.indexSignature.parameters) { + if (typeof decl.indexSignature.type !== 'undefined') { empty = false; const template: ElasticsearchDynamicTemplate = {}; template[decl.name] = { - mapping: handleDeclarationReflection(param as DeclarationReflection, new Map(generics), path, topTypeName), + mapping: handleType( + decl.indexSignature.type, + new Map(generics), path, topTypeName, + getCommentTags(decl.indexSignature)), match: '*', match_mapping_type: '*', path_match: `${path}*`, @@ -267,12 +273,24 @@ function handleDeclarationReflection(decl: DeclarationReflection, * * @param decl the DeclarationReflection to read the tags from * @param inheritedTags any tags that might have been inherited by a parent + * @param breakId the id of the previous reflection to prevent infinite recursion in some cases */ -function getCommentTags(decl: DeclarationReflection, inheritedTags: CommentTag[] = []): CommentTag[] { +function getCommentTags(decl: DeclarationReflection | SignatureReflection, + inheritedTags: CommentTag[] = [], + // tslint:disable-next-line:no-unnecessary-initializer + breakId: number | undefined = undefined, +): CommentTag[] { + if (decl.id === breakId) { + return []; + } + let out: CommentTag[] = decl.comment instanceof Comment ? typeof decl.comment.tags !== 'undefined' ? decl.comment.tags : inheritedTags : inheritedTags; if (decl.overwrites instanceof ReferenceType && decl.overwrites.reflection instanceof DeclarationReflection) { - out = arrayPriorityJoin(out, getCommentTags(decl.overwrites.reflection)); + out = arrayPriorityJoin(getCommentTags(decl.overwrites.reflection, inheritedTags, decl.id), out); + } + if (decl.inheritedFrom instanceof ReferenceType && decl.inheritedFrom.reflection instanceof DeclarationReflection) { + out = arrayPriorityJoin(getCommentTags(decl.inheritedFrom.reflection, inheritedTags, decl.id), out); } return out; @@ -357,7 +375,17 @@ function handleType(type: Type, generics: Map, path: ElasticsearchValue { // logger.log((type as any).name); if (type instanceof ArrayType) { // array is irrelevant in Elasticsearch, so just go with the element type - return handleType(type.elementType, new Map(generics), path, topTypeName, tags); + const esType = handleType(type.elementType, new Map(generics), path, topTypeName, tags); + // also merge tags of the array to the element type + // filter out the type tags lazily, this can lead to double messages for "Not implemented tag" + let newTags = tags; + if ('type' in esType) { + newTags = tags.filter((tag) => { + return !(tag.tagName === esType.type); + }); + } + + return readFieldTags(esType, path, topTypeName, newTags); } if (type.type === 'stringLiteral') { // a string literal, usually for type return readTypeTags(type.type, path, topTypeName, tags); @@ -406,11 +434,24 @@ function handleType(type: Type, generics: Map, path: */ function addAggregatable(path: string, topTypeName: string, global: boolean) { // push type.path and remove the '.' at the end of the path - const property = path.slice(0, -1) - .split('.') - .pop() as string; // cannot be undefined - (aggregations[global ? '@all' : topTypeName] as ESNestedAggregation).aggs[property] = { + if (global) { + const prop = path.slice(0, -1) + .split('.') + .pop() as string; // cannot be undefined + + return (aggregations['@all'] as ESNestedAggregation).aggs[prop.split('.') + .pop() as string] = { + terms: { + field: `${prop}.raw`, + size: 1000, + }, + }; + } + + const property = path.slice(0, -1); + + return (aggregations[topTypeName] as ESNestedAggregation).aggs[property] = { terms: { field: `${property}.raw`, size: 1000,