From 97a934d7f9cc87c2f0e6eef53279c0d7f3e2b65d Mon Sep 17 00:00:00 2001
From: Yulia Cech <yulia.cech@elastic.co>
Date: Fri, 23 Jun 2023 19:59:11 +0200
Subject: [PATCH 1/8] [Console] Add logic for query params

---
 .../src/generate_console_definitions.ts       |  92 ++--
 .../src/generate_query_params.ts              |  90 ++++
 .../types/autocomplete_definition_types.ts    |  23 +
 .../src/types/index.ts                        |  15 +
 .../src/types/specification_types.ts          | 443 ++++++++++++++++++
 5 files changed, 599 insertions(+), 64 deletions(-)
 create mode 100644 packages/kbn-generate-console-definitions/src/generate_query_params.ts
 create mode 100644 packages/kbn-generate-console-definitions/src/types/autocomplete_definition_types.ts
 create mode 100644 packages/kbn-generate-console-definitions/src/types/index.ts
 create mode 100644 packages/kbn-generate-console-definitions/src/types/specification_types.ts

diff --git a/packages/kbn-generate-console-definitions/src/generate_console_definitions.ts b/packages/kbn-generate-console-definitions/src/generate_console_definitions.ts
index f5bb7cd687c7b..2eaf066dd91a6 100644
--- a/packages/kbn-generate-console-definitions/src/generate_console_definitions.ts
+++ b/packages/kbn-generate-console-definitions/src/generate_console_definitions.ts
@@ -9,51 +9,15 @@
 import fs from 'fs';
 import Path, { join } from 'path';
 import { ToolingLog } from '@kbn/tooling-log';
-
-interface EndpointRequest {
-  name: string;
-  namespace: string;
-}
-
-interface Endpoint {
-  name: string;
-  urls: Array<{
-    methods: string[];
-    path: string;
-  }>;
-  docUrl: string;
-  request: null | EndpointRequest;
-}
-
-interface SchemaType {
-  name: {
-    name: string;
-    namespace: string;
-  };
-}
-
-interface Schema {
-  endpoints: Endpoint[];
-  types: SchemaType[];
-}
-
-interface UrlParams {
-  [key: string]: number | string;
-}
-
-interface BodyParams {
-  [key: string]: number | string;
-}
-
-interface Definition {
-  documentation?: string;
-  methods: string[];
-  patterns: string[];
-  url_params?: UrlParams;
-  data_autocomplete_rules?: BodyParams;
-}
-
-const generateMethods = (endpoint: Endpoint): string[] => {
+import { generateQueryParams } from './generate_query_params';
+import type {
+  AutocompleteBodyParams,
+  AutocompleteDefinition,
+  AutocompleteUrlParams,
+  SpecificationTypes,
+} from './types';
+
+const generateMethods = (endpoint: SpecificationTypes.Endpoint): string[] => {
   // this array consists of arrays of strings
   const methodsArray = endpoint.urls.map((url) => url.methods);
   // flatten to return array of strings
@@ -62,7 +26,7 @@ const generateMethods = (endpoint: Endpoint): string[] => {
   return [...new Set(flattenMethodsArray)];
 };
 
-const generatePatterns = (endpoint: Endpoint): string[] => {
+const generatePatterns = (endpoint: SpecificationTypes.Endpoint): string[] => {
   return endpoint.urls.map(({ path }) => {
     let pattern = path;
     // remove leading / if present
@@ -73,14 +37,14 @@ const generatePatterns = (endpoint: Endpoint): string[] => {
   });
 };
 
-const generateDocumentation = (endpoint: Endpoint): string => {
+const generateDocumentation = (endpoint: SpecificationTypes.Endpoint): string => {
   return endpoint.docUrl;
 };
 
 const generateParams = (
-  endpoint: Endpoint,
-  schema: Schema
-): { urlParams: UrlParams; bodyParams: BodyParams } | undefined => {
+  endpoint: SpecificationTypes.Endpoint,
+  schema: SpecificationTypes.Model
+): { urlParams: AutocompleteUrlParams; bodyParams: AutocompleteBodyParams } | undefined => {
   const { request } = endpoint;
   if (!request) {
     return;
@@ -91,24 +55,21 @@ const generateParams = (
   if (!requestType) {
     return;
   }
-
-  const urlParams = generateUrlParams(requestType);
+  const urlParams = generateQueryParams(requestType as SpecificationTypes.Request, schema);
   const bodyParams = generateBodyParams(requestType);
   return { urlParams, bodyParams };
 };
 
-const generateUrlParams = (requestType: SchemaType): UrlParams => {
-  return {};
-};
-
-const generateBodyParams = (requestType: SchemaType): BodyParams => {
+const generateBodyParams = (
+  requestType: SpecificationTypes.TypeDefinition
+): AutocompleteBodyParams => {
   return {};
 };
 
 const addParams = (
-  definition: Definition,
-  params: { urlParams: UrlParams; bodyParams: BodyParams }
-): Definition => {
+  definition: AutocompleteDefinition,
+  params: { urlParams: AutocompleteUrlParams; bodyParams: AutocompleteBodyParams }
+): AutocompleteDefinition => {
   const { urlParams, bodyParams } = params;
   if (urlParams && Object.keys(urlParams).length > 0) {
     definition.url_params = urlParams;
@@ -119,11 +80,14 @@ const addParams = (
   return definition;
 };
 
-const generateDefinition = (endpoint: Endpoint, schema: Schema): Definition => {
+const generateDefinition = (
+  endpoint: SpecificationTypes.Endpoint,
+  schema: SpecificationTypes.Model
+): AutocompleteDefinition => {
   const methods = generateMethods(endpoint);
   const patterns = generatePatterns(endpoint);
   const documentation = generateDocumentation(endpoint);
-  let definition: Definition = { methods, patterns, documentation };
+  let definition: AutocompleteDefinition = { methods, patterns, documentation };
   const params = generateParams(endpoint, schema);
   if (params) {
     definition = addParams(definition, params);
@@ -143,7 +107,7 @@ export function generateConsoleDefinitions({
 }) {
   const pathToSchemaFile = Path.resolve(specsRepo, 'output/schema/schema.json');
   log.info('loading the ES specification schema file');
-  const schema = JSON.parse(fs.readFileSync(pathToSchemaFile, 'utf8')) as Schema;
+  const schema = JSON.parse(fs.readFileSync(pathToSchemaFile, 'utf8')) as SpecificationTypes.Model;
 
   const { endpoints } = schema;
   log.info(`iterating over endpoints array: ${endpoints.length} endpoints`);
@@ -151,7 +115,7 @@ export function generateConsoleDefinitions({
     const { name } = endpoint;
     log.info(name);
     const definition = generateDefinition(endpoint, schema);
-    const fileContent: { [name: string]: Definition } = {
+    const fileContent: { [name: string]: AutocompleteDefinition } = {
       [name]: definition,
     };
     fs.writeFileSync(
diff --git a/packages/kbn-generate-console-definitions/src/generate_query_params.ts b/packages/kbn-generate-console-definitions/src/generate_query_params.ts
new file mode 100644
index 0000000000000..91123913d17d8
--- /dev/null
+++ b/packages/kbn-generate-console-definitions/src/generate_query_params.ts
@@ -0,0 +1,90 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { UrlParamValue } from './types/autocomplete_definition_types';
+import type { AutocompleteUrlParams, SpecificationTypes } from './types';
+export const generateQueryParams = (
+  requestType: SpecificationTypes.Request,
+  schema: SpecificationTypes.Model
+): AutocompleteUrlParams => {
+  let urlParams = {} as AutocompleteUrlParams;
+  const { types } = schema;
+  const { attachedBehaviors, query } = requestType;
+  // if there are any attached behaviors, iterate over each and find its type
+  if (attachedBehaviors) {
+    for (const attachedBehavior of attachedBehaviors) {
+      const foundBehavior = types.find((type) => type.name.name === attachedBehavior);
+      if (foundBehavior) {
+        const behaviorType = foundBehavior as SpecificationTypes.Interface;
+        // if there are any properties in the behavior type, iterate over each and add it to url params
+        const { properties } = behaviorType;
+        urlParams = convertProperties(properties, urlParams);
+      }
+    }
+  }
+
+  // iterate over properties in query
+  urlParams = convertProperties(query, urlParams);
+
+  return urlParams;
+};
+
+const convertInstanceOf = (
+  type: SpecificationTypes.InstanceOf,
+  serverDefault: SpecificationTypes.Property['serverDefault']
+): UrlParamValue => {
+  const {
+    type: { name: propertyName },
+  } = type;
+  // text property
+  if (propertyName === 'string') {
+    // add default value if any
+    return serverDefault ?? '';
+  }
+  // boolean
+  else if (propertyName === 'boolean') {
+    return '__flag__';
+  }
+  // duration
+  else if (propertyName === 'Duration') {
+    // add default value if any
+    return serverDefault ?? '';
+  }
+  // names
+  else if (propertyName === 'Names') {
+    return [];
+  }
+  return '';
+};
+
+const convertProperties = (
+  properties: SpecificationTypes.Property[],
+  urlParams: AutocompleteUrlParams
+): AutocompleteUrlParams => {
+  for (const property of properties) {
+    const { name, serverDefault, type } = property;
+    const { kind } = type;
+    if (kind === 'instance_of') {
+      urlParams[name] = convertInstanceOf(type, serverDefault);
+    } else if (kind === 'union_of') {
+      const { items } = type;
+      const itemValues = new Set();
+      for (const item of items) {
+        if (item.kind === 'instance_of') {
+          itemValues.add(convertInstanceOf(item, serverDefault));
+        } else if (item.kind === 'array_of') {
+          if (item.value.kind === 'instance_of') {
+            itemValues.add(convertInstanceOf(item.value, serverDefault));
+          }
+        }
+      }
+      urlParams[name] = itemValues as unknown as UrlParamValue;
+    }
+  }
+  return urlParams;
+};
diff --git a/packages/kbn-generate-console-definitions/src/types/autocomplete_definition_types.ts b/packages/kbn-generate-console-definitions/src/types/autocomplete_definition_types.ts
new file mode 100644
index 0000000000000..5f3dff755abcf
--- /dev/null
+++ b/packages/kbn-generate-console-definitions/src/types/autocomplete_definition_types.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+export type UrlParamValue = number | string | number[] | string[] | boolean;
+export interface AutocompleteUrlParams {
+  [key: string]: UrlParamValue;
+}
+
+export interface AutocompleteBodyParams {
+  [key: string]: number | string;
+}
+
+export interface AutocompleteDefinition {
+  documentation?: string;
+  methods: string[];
+  patterns: string[];
+  url_params?: AutocompleteUrlParams;
+  data_autocomplete_rules?: AutocompleteBodyParams;
+}
diff --git a/packages/kbn-generate-console-definitions/src/types/index.ts b/packages/kbn-generate-console-definitions/src/types/index.ts
new file mode 100644
index 0000000000000..a182592350681
--- /dev/null
+++ b/packages/kbn-generate-console-definitions/src/types/index.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export type {
+  AutocompleteDefinition,
+  AutocompleteUrlParams,
+  AutocompleteBodyParams,
+} from './autocomplete_definition_types';
+
+export * as SpecificationTypes from './specification_types';
diff --git a/packages/kbn-generate-console-definitions/src/types/specification_types.ts b/packages/kbn-generate-console-definitions/src/types/specification_types.ts
new file mode 100644
index 0000000000000..eb35d4fa3fa7d
--- /dev/null
+++ b/packages/kbn-generate-console-definitions/src/types/specification_types.ts
@@ -0,0 +1,443 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+/**
+ * --------------- THIS FILE IS COPIED FROM ES SPECIFICATION REPO -------------------
+ *
+ */
+
+
+/**
+ * The name of a type, composed of a simple name and a namespace. Hierarchical namespace elements are separated by
+ * a dot, e.g 'cat.cat_aliases'.
+ *
+ * Builtin namespaces:
+ * - "generic" for type names that are generic parameter values from the enclosing type.
+ * - "internal" for primitive and builtin types (e.g. Id, IndexName, etc)
+ *    Builtin types:
+ *    - boolean,
+ *    - string,
+ *    - number: a 64bits floating point number. Additional types will be added for integers.
+ *    - null: the null value. Since JS distinguishes undefined and null, some APIs make use of this value.
+ *    - object: used to represent "any". We may forbid it at some point. UserDefinedValue should be used for user data.
+ */
+export interface TypeName {
+  namespace: string;
+  name: string;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Value types
+
+// Note: "required" is part of Property. This means we can have optional properties but we can't have null entries in
+// containers (array and dictionary), which doesn't seem to be needed.
+//
+// The 'kind' property is used to tag and disambiguate union type members, and allow type-safe pattern matching in TS:
+// see https://blog.logrocket.com/pattern-matching-and-type-safety-in-typescript-1da1231a2e34/
+// and https://medium.com/@fillopeter/pattern-matching-with-typescript-done-right-94049ddd671c
+
+/**
+ * Type of a value. Used both for property types and nested type definitions.
+ */
+export type ValueOf =
+  | InstanceOf
+  | ArrayOf
+  | UnionOf
+  | DictionaryOf
+  | UserDefinedValue
+  | LiteralValue;
+
+/**
+ * A single value
+ */
+export interface InstanceOf {
+  kind: 'instance_of';
+  type: TypeName;
+  /** generic parameters: either concrete types or open parameters from the enclosing type */
+  generics?: ValueOf[];
+}
+
+/**
+ * An array
+ */
+export interface ArrayOf {
+  kind: 'array_of';
+  value: ValueOf;
+}
+
+/**
+ * One of several possible types which don't necessarily have a common superinterface
+ */
+export interface UnionOf {
+  kind: 'union_of';
+  items: ValueOf[];
+}
+
+/**
+ * A dictionary (or map).  The key is a string or a number (or a union thereof), possibly through an alias.
+ *
+ * If `singleKey` is true, then this dictionary can only have a single key. This is a common pattern in ES APIs,
+ * used to associate a value to a field name or some other identifier.
+ */
+export interface DictionaryOf {
+  kind: 'dictionary_of';
+  key: ValueOf;
+  value: ValueOf;
+  singleKey: boolean;
+}
+
+/**
+ * A user defined value. To be used when bubbling a generic parameter up to the top-level interface is
+ * inconvenient or impossible (e.g. for lists of user-defined values of possibly different types).
+ *
+ * Clients will allow providing a serializer/deserializer when reading/writing properties of this type,
+ * and should also accept raw json.
+ *
+ * Think twice before using this as it defeats the purpose of a strongly typed API, and deserialization
+ * will also require to buffer raw JSON data which may have performance implications.
+ */
+export interface UserDefinedValue {
+  kind: 'user_defined_value';
+}
+
+/**
+ * A literal value. This is used for tagged unions, where each type member of a union has a 'type'
+ * attribute that defines its kind. This metamodel heavily uses this approach with its 'kind' attributes.
+ *
+ * It may later be used to set a property to a constant value, which is why it accepts not only strings but also
+ * other primitive types.
+ */
+export interface LiteralValue {
+  kind: 'literal_value';
+  value: string | number | boolean;
+}
+
+/**
+ * An interface or request interface property.
+ */
+export interface Property {
+  name: string;
+  type: ValueOf;
+  required: boolean;
+  description?: string;
+  docUrl?: string;
+  docId?: string;
+  since?: string;
+  serverDefault?: boolean | string | number | string[] | number[];
+  deprecation?: Deprecation;
+  availability?: Availabilities;
+  stability?: Stability;
+  /**
+   * If specified takes precedence over `name` when generating code. `name` is always the value
+   * to be sent over the wire
+   */
+  codegenName?: string;
+  /** An optional set of aliases for `name` */
+  aliases?: string[];
+  /** If the enclosing interface is a variants container, is this a property of the container and not a variant? */
+  containerProperty?: boolean;
+  /** If this property has a quirk that needs special attention, give a short explanation about it */
+  esQuirk?: string;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Type definitions
+
+export type TypeDefinition = Interface | Request | Response | Enum | TypeAlias;
+
+// ------------------------------------------------------------------------------------------------
+
+/**
+ * Common attributes for all type definitions
+ */
+export interface BaseType {
+  name: TypeName;
+  description?: string;
+  /** Link to public documentation */
+  docUrl?: string;
+  docId?: string;
+  deprecation?: Deprecation;
+  /** If this endpoint has a quirk that needs special attention, give a short explanation about it */
+  esQuirk?: string;
+  kind: string;
+  /** Variant name for externally tagged variants */
+  variantName?: string;
+  /**
+   * Additional identifiers for use by code generators. Usage depends on the actual type:
+   * - on unions (modeled as alias(union_of)), these are identifiers for the union members
+   * - for additional properties, this is the name of the dict that holds these properties
+   * - for additional property, this is the name of the key and value fields that hold the
+   *   additional property
+   */
+  codegenNames?: string[];
+  /**
+   * Location of an item. The path is relative to the "specification" directory, e.g "_types/common.ts#L1-L2"
+   */
+  specLocation: string;
+}
+
+export type Variants = ExternalTag | InternalTag | Container;
+
+export interface VariantBase {
+  /**
+   * Is this variant type open to extensions? Default to false. Used for variants that can
+   * be extended with plugins. If true, target clients should allow for additional variants
+   * with a variant tag outside the ones defined in the spec and arbitrary data as the value.
+   */
+  nonExhaustive?: boolean;
+}
+
+export interface ExternalTag extends VariantBase {
+  kind: 'external_tag';
+}
+
+export interface InternalTag extends VariantBase {
+  kind: 'internal_tag';
+  /* Name of the property that holds the variant tag */
+  tag: string;
+  /* Default value for the variant tag if it's missing */
+  defaultTag?: string;
+}
+
+export interface Container extends VariantBase {
+  kind: 'container';
+}
+
+/**
+ * Inherits clause (aka extends or implements) for an interface or request
+ */
+export interface Inherits {
+  type: TypeName;
+  generics?: ValueOf[];
+}
+
+/**
+ * An interface type
+ */
+export interface Interface extends BaseType {
+  kind: 'interface';
+  /**
+   * Open generic parameters. The name is that of the parameter, the namespace is an arbitrary value that allows
+   * this fully qualified type name to be used when this open generic parameter is used in property's type.
+   */
+  generics?: TypeName[];
+  inherits?: Inherits;
+  implements?: Inherits[];
+
+  /**
+   * Behaviors directly implemented by this interface
+   */
+  behaviors?: Inherits[];
+
+  /**
+   * Behaviors attached to this interface, coming from the interface itself (see `behaviors`)
+   * or from inherits and implements ancestors
+   */
+  attachedBehaviors?: string[];
+  properties: Property[];
+  /**
+   * The property that can be used as a shortcut for the entire data structure in the JSON.
+   */
+  shortcutProperty?: string;
+
+  /** Identify containers */
+  variants?: Container;
+}
+
+/**
+ * A request type
+ */
+export interface Request extends BaseType {
+  // Note: does not extend Interface as properties are split across path, query and body
+  kind: 'request';
+  generics?: TypeName[];
+  /** The parent defines additional body properties that are added to the body, that has to be a PropertyBody */
+  inherits?: Inherits;
+  implements?: Inherits[];
+  /** URL path properties */
+  path: Property[];
+  /** Query string properties */
+  query: Property[];
+  // FIXME: we need an annotation that lists query params replaced by a body property so that we can skip them.
+  // Examples on _search: sort -> sort, _source -> (_source, _source_include, _source_exclude)
+  // Or can we say that implicitly a body property replaces all path params starting with its name?
+  // Is there a priority rule between path and body parameters?
+  //
+  // We can also pull path parameter descriptions on body properties they replace
+
+  /**
+   * Body type. Most often a list of properties (that can extend those of the inherited interface, see above), except for a
+   * few specific cases that use other types such as bulk (array) or create (generic parameter). Or NoBody for requests
+   * that don't have a body.
+   */
+  body: Body;
+  behaviors?: Inherits[];
+  attachedBehaviors?: string[];
+}
+
+/**
+ * A response type
+ */
+export interface Response extends BaseType {
+  kind: 'response';
+  generics?: TypeName[];
+  body: Body;
+  behaviors?: Inherits[];
+  attachedBehaviors?: string[];
+  exceptions?: ResponseException[];
+}
+
+export interface ResponseException {
+  description?: string;
+  body: Body;
+  statusCodes: number[];
+}
+
+export type Body = ValueBody | PropertiesBody | NoBody;
+
+export interface ValueBody {
+  kind: 'value';
+  value: ValueOf;
+  codegenName?: string;
+}
+
+export interface PropertiesBody {
+  kind: 'properties';
+  properties: Property[];
+}
+
+export interface NoBody {
+  kind: 'no_body';
+}
+
+/**
+ * An enumeration member.
+ *
+ * When enumeration members can become ambiguous when translated to an identifier, the `name` property will be a good
+ * identifier name, and `stringValue` will be the string value to use on the wire.
+ * See DateMathTimeUnit for an example of this, which have members for "m" (minute) and "M" (month).
+ */
+export interface EnumMember {
+  /** The identifier to use for this enum */
+  name: string;
+  /** An optional set of aliases for `name` */
+  aliases?: string[];
+  /**
+   * If specified takes precedence over `name` when generating code. `name` is always the value
+   * to be sent over the wire
+   */
+  codegenName?: string;
+  description?: string;
+  deprecation?: Deprecation;
+  since?: string;
+}
+
+/**
+ * An enumeration
+ */
+export interface Enum extends BaseType {
+  kind: 'enum';
+  /**
+   * If the enum is open, it means that other than the specified values it can accept an arbitrary value.
+   * If this property is not present, it means that the enum is not open (in other words, is closed).
+   */
+  isOpen?: boolean;
+  members: EnumMember[];
+}
+
+/**
+ * An alias for an existing type.
+ */
+export interface TypeAlias extends BaseType {
+  kind: 'type_alias';
+  type: ValueOf;
+  /** generic parameters: either concrete types or open parameters from the enclosing type */
+  generics?: TypeName[];
+  /** Only applicable to `union_of` aliases: identify typed_key unions (external) and variant inventories (internal) */
+  variants?: InternalTag | ExternalTag;
+}
+
+// ------------------------------------------------------------------------------------------------
+
+export enum Stability {
+  stable = 'stable',
+  beta = 'beta',
+  experimental = 'experimental',
+}
+export enum Visibility {
+  public = 'public',
+  feature_flag = 'feature_flag',
+  private = 'private',
+}
+
+export interface Deprecation {
+  version: string;
+  description: string;
+}
+
+export interface Availabilities {
+  stack?: Availability;
+  serverless?: Availability;
+}
+
+export interface Availability {
+  since?: string;
+  featureFlag?: string;
+  stability?: Stability;
+  visibility?: Visibility;
+}
+
+export interface Endpoint {
+  name: string;
+  description: string;
+  docUrl: string;
+  docId?: string;
+  deprecation?: Deprecation;
+  availability: Availabilities;
+
+  /**
+   * If the request value is `null` it means that there is not yet a
+   * request type definition for this endpoint.
+   */
+  request: TypeName | null;
+  requestBodyRequired: boolean; // Not sure this is useful
+
+  /**
+   * If the response value is `null` it means that there is not yet a
+   * response type definition for this endpoint.
+   */
+  response: TypeName | null;
+
+  urls: UrlTemplate[];
+
+  /**
+   * The version when this endpoint reached its current stability level.
+   * Missing data means "forever", i.e. before any of the target client versions produced from this spec.
+   */
+  since?: string;
+  stability?: Stability;
+  visibility?: Visibility;
+  featureFlag?: string;
+  requestMediaType?: string[];
+  responseMediaType?: string[];
+  privileges?: {
+    index?: string[];
+    cluster?: string[];
+  };
+}
+
+export interface UrlTemplate {
+  path: string;
+  methods: string[];
+  deprecation?: Deprecation;
+}
+
+export interface Model {
+  types: TypeDefinition[];
+  endpoints: Endpoint[];
+}

From de8e8e3d97bdae8f6150a80b2e39e6c73afb4ab7 Mon Sep 17 00:00:00 2001
From: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Date: Mon, 26 Jun 2023 12:38:31 +0000
Subject: [PATCH 2/8] [CI] Auto-commit changed files from 'node
 scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

---
 .../src/types/specification_types.ts                             | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/kbn-generate-console-definitions/src/types/specification_types.ts b/packages/kbn-generate-console-definitions/src/types/specification_types.ts
index eb35d4fa3fa7d..b9e61ded06448 100644
--- a/packages/kbn-generate-console-definitions/src/types/specification_types.ts
+++ b/packages/kbn-generate-console-definitions/src/types/specification_types.ts
@@ -11,7 +11,6 @@
  *
  */
 
-
 /**
  * The name of a type, composed of a simple name and a namespace. Hierarchical namespace elements are separated by
  * a dot, e.g 'cat.cat_aliases'.

From 26fc1469857092d922b60dbe0cd9555d9896a176 Mon Sep 17 00:00:00 2001
From: Yulia Cech <yulia.cech@elastic.co>
Date: Tue, 27 Jun 2023 18:22:11 +0200
Subject: [PATCH 3/8] [Console] Implement url params conversion logic

---
 .../src/generate_console_definitions.ts       |  8 +-
 .../src/generate_query_params.ts              | 81 +++++++++++++------
 .../types/autocomplete_definition_types.ts    |  4 +-
 .../src/utils.ts                              | 17 ++++
 4 files changed, 80 insertions(+), 30 deletions(-)
 create mode 100644 packages/kbn-generate-console-definitions/src/utils.ts

diff --git a/packages/kbn-generate-console-definitions/src/generate_console_definitions.ts b/packages/kbn-generate-console-definitions/src/generate_console_definitions.ts
index 2eaf066dd91a6..1d4918f44c6b1 100644
--- a/packages/kbn-generate-console-definitions/src/generate_console_definitions.ts
+++ b/packages/kbn-generate-console-definitions/src/generate_console_definitions.ts
@@ -16,6 +16,7 @@ import type {
   AutocompleteUrlParams,
   SpecificationTypes,
 } from './types';
+import { findTypeDefinition } from './utils';
 
 const generateMethods = (endpoint: SpecificationTypes.Endpoint): string[] => {
   // this array consists of arrays of strings
@@ -49,9 +50,7 @@ const generateParams = (
   if (!request) {
     return;
   }
-  const requestType = schema.types.find(
-    ({ name: { name, namespace } }) => name === request.name && namespace === request.namespace
-  );
+  const requestType = findTypeDefinition(schema, request);
   if (!requestType) {
     return;
   }
@@ -87,11 +86,12 @@ const generateDefinition = (
   const methods = generateMethods(endpoint);
   const patterns = generatePatterns(endpoint);
   const documentation = generateDocumentation(endpoint);
-  let definition: AutocompleteDefinition = { methods, patterns, documentation };
+  let definition: AutocompleteDefinition = {};
   const params = generateParams(endpoint, schema);
   if (params) {
     definition = addParams(definition, params);
   }
+  definition = { ...definition, methods, patterns, documentation };
 
   return definition;
 };
diff --git a/packages/kbn-generate-console-definitions/src/generate_query_params.ts b/packages/kbn-generate-console-definitions/src/generate_query_params.ts
index 91123913d17d8..8859dd9c2d104 100644
--- a/packages/kbn-generate-console-definitions/src/generate_query_params.ts
+++ b/packages/kbn-generate-console-definitions/src/generate_query_params.ts
@@ -8,6 +8,7 @@
 
 import { UrlParamValue } from './types/autocomplete_definition_types';
 import type { AutocompleteUrlParams, SpecificationTypes } from './types';
+import { findTypeDefinition } from './utils';
 export const generateQueryParams = (
   requestType: SpecificationTypes.Request,
   schema: SpecificationTypes.Model
@@ -23,24 +24,24 @@ export const generateQueryParams = (
         const behaviorType = foundBehavior as SpecificationTypes.Interface;
         // if there are any properties in the behavior type, iterate over each and add it to url params
         const { properties } = behaviorType;
-        urlParams = convertProperties(properties, urlParams);
+        urlParams = convertProperties(properties, urlParams, schema);
       }
     }
   }
 
   // iterate over properties in query
-  urlParams = convertProperties(query, urlParams);
+  urlParams = convertProperties(query, urlParams, schema);
 
   return urlParams;
 };
 
 const convertInstanceOf = (
   type: SpecificationTypes.InstanceOf,
-  serverDefault: SpecificationTypes.Property['serverDefault']
-): UrlParamValue => {
-  const {
-    type: { name: propertyName },
-  } = type;
+  serverDefault: SpecificationTypes.Property['serverDefault'],
+  schema: SpecificationTypes.Model
+): UrlParamValue | undefined => {
+  const { type: typeName } = type;
+  const { name: propertyName } = typeName;
   // text property
   if (propertyName === 'string') {
     // add default value if any
@@ -58,33 +59,65 @@ const convertInstanceOf = (
   // names
   else if (propertyName === 'Names') {
     return [];
+  } else {
+    // if it's a defined type, try to convert it
+    const definedType = findTypeDefinition(schema, typeName);
+    if (definedType) {
+      // if it's enum
+      if (definedType.kind === 'enum') {
+        return convertEnum(definedType as SpecificationTypes.Enum);
+      } else if (definedType.kind === 'type_alias') {
+        const aliasValueOf = definedType.type;
+        return convertValueOf(aliasValueOf, serverDefault, schema);
+      }
+    }
   }
-  return '';
 };
 
 const convertProperties = (
   properties: SpecificationTypes.Property[],
-  urlParams: AutocompleteUrlParams
+  urlParams: AutocompleteUrlParams,
+  schema: SpecificationTypes.Model
 ): AutocompleteUrlParams => {
   for (const property of properties) {
     const { name, serverDefault, type } = property;
-    const { kind } = type;
-    if (kind === 'instance_of') {
-      urlParams[name] = convertInstanceOf(type, serverDefault);
-    } else if (kind === 'union_of') {
-      const { items } = type;
-      const itemValues = new Set();
-      for (const item of items) {
-        if (item.kind === 'instance_of') {
-          itemValues.add(convertInstanceOf(item, serverDefault));
-        } else if (item.kind === 'array_of') {
-          if (item.value.kind === 'instance_of') {
-            itemValues.add(convertInstanceOf(item.value, serverDefault));
-          }
+    const convertedValue = convertValueOf(type, serverDefault, schema);
+    urlParams[name] = convertedValue ?? '';
+  }
+  return urlParams;
+};
+
+const convertValueOf = (
+  valueOf: SpecificationTypes.ValueOf,
+  serverDefault: SpecificationTypes.Property['serverDefault'],
+  schema: SpecificationTypes.Model
+): UrlParamValue | undefined => {
+  const { kind } = valueOf;
+  if (kind === 'instance_of') {
+    return convertInstanceOf(valueOf, serverDefault, schema);
+  } else if (kind === 'union_of') {
+    const { items } = valueOf;
+    const itemValues = new Set();
+    for (const item of items) {
+      if (item.kind === 'instance_of') {
+        const convertedValue = convertInstanceOf(item, serverDefault, schema);
+        if (convertedValue instanceof Array) {
+          convertedValue.forEach((v) => itemValues.add(v));
+        } else itemValues.add(convertedValue);
+      } else if (item.kind === 'array_of') {
+        if (item.value.kind === 'instance_of') {
+          const convertedValue = convertInstanceOf(item.value, serverDefault, schema);
+          if (convertedValue instanceof Array) {
+            convertedValue.forEach((v) => itemValues.add(v));
+          } else itemValues.add(convertedValue);
         }
       }
-      urlParams[name] = itemValues as unknown as UrlParamValue;
     }
+    return [...itemValues] as UrlParamValue;
   }
-  return urlParams;
+};
+
+const convertEnum = (enumDefinition: SpecificationTypes.Enum): UrlParamValue => {
+  const { members } = enumDefinition;
+  return members.map((member) => member.name);
 };
diff --git a/packages/kbn-generate-console-definitions/src/types/autocomplete_definition_types.ts b/packages/kbn-generate-console-definitions/src/types/autocomplete_definition_types.ts
index 5f3dff755abcf..edbb9bd74d9a8 100644
--- a/packages/kbn-generate-console-definitions/src/types/autocomplete_definition_types.ts
+++ b/packages/kbn-generate-console-definitions/src/types/autocomplete_definition_types.ts
@@ -16,8 +16,8 @@ export interface AutocompleteBodyParams {
 
 export interface AutocompleteDefinition {
   documentation?: string;
-  methods: string[];
-  patterns: string[];
+  methods?: string[];
+  patterns?: string[];
   url_params?: AutocompleteUrlParams;
   data_autocomplete_rules?: AutocompleteBodyParams;
 }
diff --git a/packages/kbn-generate-console-definitions/src/utils.ts b/packages/kbn-generate-console-definitions/src/utils.ts
new file mode 100644
index 0000000000000..29c24e63fa58f
--- /dev/null
+++ b/packages/kbn-generate-console-definitions/src/utils.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { SpecificationTypes } from './types';
+export const findTypeDefinition = (
+  schema: SpecificationTypes.Model,
+  typeName: SpecificationTypes.TypeName
+): SpecificationTypes.TypeDefinition | undefined => {
+  return schema.types.find(
+    (type) => type.name.name === typeName.name && type.name.namespace === typeName.namespace
+  );
+};

From 2e61a7a6d7e570455c88d7bbf5ed232bc12d63df Mon Sep 17 00:00:00 2001
From: Yulia Cech <yulia.cech@elastic.co>
Date: Thu, 29 Jun 2023 12:11:33 +0200
Subject: [PATCH 4/8] [Console] Clean up the code, add comments, refactorings

---
 .../src/generate_query_params.ts              | 204 +++++++++++++-----
 1 file changed, 149 insertions(+), 55 deletions(-)

diff --git a/packages/kbn-generate-console-definitions/src/generate_query_params.ts b/packages/kbn-generate-console-definitions/src/generate_query_params.ts
index 8859dd9c2d104..99d59d2d318b0 100644
--- a/packages/kbn-generate-console-definitions/src/generate_query_params.ts
+++ b/packages/kbn-generate-console-definitions/src/generate_query_params.ts
@@ -6,9 +6,51 @@
  * Side Public License, v 1.
  */
 
+/**
+ * Types that are important for query params conversion:
+ * TypeDefinition = Interface | Request | Response | Enum | TypeAlias
+ * ValueOf = InstanceOf | ArrayOf | UnionOf | DictionaryOf | UserDefinedValue | LiteralValue;
+ *
+ * Conversion steps:
+ * 1. The schema has a property  `endpoints` which is "Endpoint[]"
+ * 2. Each "Endpoint" has a property `request` which is "TypeName"
+ * 3. Using "TypeName" we find the "TypeDefinition" in the property `types` of the schema
+ * 4. the "TypeDefinition" is cast to "Request"
+ * - "Request" has a property `query` which is "Property[]"
+ * - "Request" has a property `attachedBehaviours` which is "string[]"
+ *    With "string" we find a "TypeDefinition" that is "Interface"
+ *    This "Interface" has a property `properties` which is "Property[]"
+ * 5. Each "Property" (from both `query` and `attachedBehaviours`) now can be converted
+ * 6. Each "Property" has a property `type` that is "ValueOf"
+ * 7. If "ValueOf" can be one of "InstanceOf", "ArrayOf", "UnionOf", "DictionaryOf", "UserDefinedValue", "LiteralValue"
+ * - "InstanceOf": it has a property `type` which is a "TypeName"
+ *   - if "TypeName" has a `namespace` = "_builtins" then it's a primitive type like "string" -> convert according to set rules for primitives
+ *   - if "TypeName" has a `namespace` = "_types" then it's a defined type that can be found in the schema
+ *     - the found "TypeDefinition" can be either "Enum" or "TypeAlias" (not "Interface", "Request" or "Response")
+ *       - if it's "TypeAlias", it has a property `type` which is "ValueOf" -> handle it as "ValueOf" (recursion)
+ *       - if it's "Enum", it has a property `members` which is "EnumMember[]" -> convert each "EnumMember" (only need `name` property)
+ * - "ArrayOf": it has a property `value` which is "ValueOf" -> convert as "ValueOf"
+ * - "UnionOf": it has a property `items` which is "ValueOf[]" -> convert each as "ValueOf"
+ * - "DictionaryOf": not used for query params
+ * - "UserDefinedValue": not used for query params
+ * - "LiteralValue": it has `value` that is `string`, `number` or `boolean`
+ *
+ * Autocomplete definitions currently work with 2 url param types:
+ * - "__flag__" for a boolean (suggesting value 'true' and 'false')
+ * - list of options in an array, for example ['30s', '-1', '0'], suggesting all 3 values in a list
+ * If there is only a default value, we need to wrap it in an array, so that this value is displayed in a suggestion (similar to the list).
+ * Numbers need to be converted to strings, otherwise they are not displayed as suggestions.
+ *
+ */
+
 import { UrlParamValue } from './types/autocomplete_definition_types';
 import type { AutocompleteUrlParams, SpecificationTypes } from './types';
 import { findTypeDefinition } from './utils';
+
+const booleanFlagString = '__flag__';
+const trueValueString = String(true);
+const falseValueString = String(false);
+
 export const generateQueryParams = (
   requestType: SpecificationTypes.Request,
   schema: SpecificationTypes.Model
@@ -21,49 +63,82 @@ export const generateQueryParams = (
     for (const attachedBehavior of attachedBehaviors) {
       const foundBehavior = types.find((type) => type.name.name === attachedBehavior);
       if (foundBehavior) {
+        // attached behaviours are interfaces
         const behaviorType = foundBehavior as SpecificationTypes.Interface;
-        // if there are any properties in the behavior type, iterate over each and add it to url params
+        // if there are any properties in the behavior, iterate over each and add it to url params
         const { properties } = behaviorType;
         urlParams = convertProperties(properties, urlParams, schema);
       }
     }
   }
 
-  // iterate over properties in query
+  // iterate over properties in query and add it to url params
   urlParams = convertProperties(query, urlParams, schema);
 
   return urlParams;
 };
 
+const convertProperties = (
+  properties: SpecificationTypes.Property[],
+  urlParams: AutocompleteUrlParams,
+  schema: SpecificationTypes.Model
+): AutocompleteUrlParams => {
+  for (const property of properties) {
+    const { name, serverDefault, type } = property;
+    // property has `type` which is `ValueOf`
+    const convertedValue = convertValueOf(type, serverDefault, schema);
+    urlParams[name] = convertedValue ?? '';
+  }
+  return urlParams;
+};
+
+const convertValueOf = (
+  valueOf: SpecificationTypes.ValueOf,
+  serverDefault: SpecificationTypes.Property['serverDefault'],
+  schema: SpecificationTypes.Model
+): UrlParamValue | undefined => {
+  const { kind } = valueOf;
+  if (kind === 'instance_of') {
+    return convertInstanceOf(valueOf, serverDefault, schema);
+  } else if (kind === 'array_of') {
+    return convertArrayOf(valueOf, serverDefault, schema);
+  } else if (kind === 'union_of') {
+    return convertUnionOf(valueOf, serverDefault, schema);
+  } else if (kind === 'literal_value') {
+    return convertLiteralValue(valueOf);
+  }
+  // for query params we can ignore 'dictionary_of' and 'user_defined_value'
+};
+
 const convertInstanceOf = (
   type: SpecificationTypes.InstanceOf,
   serverDefault: SpecificationTypes.Property['serverDefault'],
   schema: SpecificationTypes.Model
 ): UrlParamValue | undefined => {
   const { type: typeName } = type;
-  const { name: propertyName } = typeName;
-  // text property
-  if (propertyName === 'string') {
-    // add default value if any
-    return serverDefault ?? '';
-  }
-  // boolean
-  else if (propertyName === 'boolean') {
-    return '__flag__';
-  }
-  // duration
-  else if (propertyName === 'Duration') {
-    // add default value if any
-    return serverDefault ?? '';
-  }
-  // names
-  else if (propertyName === 'Names') {
-    return [];
+  const { name: propertyName, namespace } = typeName;
+  if (namespace === '_builtins') {
+    /**
+     * - `string`
+     * - `boolean`
+     * - `number`
+     * - `null` // ignore for query params
+     * - `void` // ignore for query params
+     * - `binary` // ignore for query params
+     */
+
+    if (propertyName === 'boolean') {
+      // boolean is converted to a flag param
+      return booleanFlagString;
+    } else {
+      // if default value, convert to string and put in an array
+      return serverDefault ? [serverDefault + ''] : '';
+    }
   } else {
     // if it's a defined type, try to convert it
     const definedType = findTypeDefinition(schema, typeName);
     if (definedType) {
-      // if it's enum
+      // TypeDefinition can only be Enum or TypeAlias
       if (definedType.kind === 'enum') {
         return convertEnum(definedType as SpecificationTypes.Enum);
       } else if (definedType.kind === 'type_alias') {
@@ -74,50 +149,69 @@ const convertInstanceOf = (
   }
 };
 
-const convertProperties = (
-  properties: SpecificationTypes.Property[],
-  urlParams: AutocompleteUrlParams,
+const convertArrayOf = (
+  type: SpecificationTypes.ArrayOf,
+  serverDefault: SpecificationTypes.Property['serverDefault'],
   schema: SpecificationTypes.Model
-): AutocompleteUrlParams => {
-  for (const property of properties) {
-    const { name, serverDefault, type } = property;
-    const convertedValue = convertValueOf(type, serverDefault, schema);
-    urlParams[name] = convertedValue ?? '';
-  }
-  return urlParams;
+): UrlParamValue | undefined => {
+  const { value } = type;
+  // simply convert the value of an array item
+  return convertValueOf(value, serverDefault, schema);
 };
 
-const convertValueOf = (
-  valueOf: SpecificationTypes.ValueOf,
+const convertUnionOf = (
+  type: SpecificationTypes.UnionOf,
   serverDefault: SpecificationTypes.Property['serverDefault'],
   schema: SpecificationTypes.Model
 ): UrlParamValue | undefined => {
-  const { kind } = valueOf;
-  if (kind === 'instance_of') {
-    return convertInstanceOf(valueOf, serverDefault, schema);
-  } else if (kind === 'union_of') {
-    const { items } = valueOf;
-    const itemValues = new Set();
-    for (const item of items) {
-      if (item.kind === 'instance_of') {
-        const convertedValue = convertInstanceOf(item, serverDefault, schema);
-        if (convertedValue instanceof Array) {
-          convertedValue.forEach((v) => itemValues.add(v));
-        } else itemValues.add(convertedValue);
-      } else if (item.kind === 'array_of') {
-        if (item.value.kind === 'instance_of') {
-          const convertedValue = convertInstanceOf(item.value, serverDefault, schema);
-          if (convertedValue instanceof Array) {
-            convertedValue.forEach((v) => itemValues.add(v));
-          } else itemValues.add(convertedValue);
-        }
-      }
-    }
-    return [...itemValues] as UrlParamValue;
+  const { items } = type;
+  const itemValues = new Set();
+  for (const item of items) {
+    // each item is ValueOf
+    const convertedValue = convertValueOf(item, serverDefault, schema);
+    // flatten array if needed
+    if (convertedValue instanceof Array) {
+      convertedValue.forEach((v) => itemValues.add(v));
+    } else itemValues.add(convertedValue);
+  }
+
+  // if an empty string is in values, delete it
+  if (itemValues.has('')) {
+    itemValues.delete('');
   }
+
+  // if there is a flag in the values, convert it to "true" + "false"
+  if (itemValues.size > 1 && itemValues.has(booleanFlagString)) {
+    itemValues.delete(booleanFlagString);
+    itemValues.add(trueValueString);
+    itemValues.add(falseValueString);
+  }
+
+  // if only 2 values ("true","false"), convert back to a flag
+  // that can happen if the values before were ("true", "__flag__") or ("false", "__flag__")
+  if (
+    itemValues.size === 2 &&
+    itemValues.has(trueValueString) &&
+    itemValues.has(falseValueString)
+  ) {
+    itemValues.clear();
+    itemValues.add(booleanFlagString);
+  }
+
+  // if only 1 element that is a flag, don't put it in an array
+  if (itemValues.size === 1 && itemValues.has(booleanFlagString)) {
+    return itemValues.values().next().value;
+  }
+  return [...itemValues] as UrlParamValue;
+};
+
+const convertLiteralValue = (type: SpecificationTypes.LiteralValue): UrlParamValue | undefined => {
+  // convert the value to a string
+  return [type.value + ''];
 };
 
 const convertEnum = (enumDefinition: SpecificationTypes.Enum): UrlParamValue => {
   const { members } = enumDefinition;
+  // only need the `name` property
   return members.map((member) => member.name);
 };

From 4b9b107c0231476e366ea00268d1a34fe119890a Mon Sep 17 00:00:00 2001
From: Yulia Cech <yulia.cech@elastic.co>
Date: Thu, 29 Jun 2023 15:57:21 +0200
Subject: [PATCH 5/8] [Console] Unit tests for query params conversion logic

---
 .../src/generate_query_params.test.ts         | 327 ++++++++++++++++++
 1 file changed, 327 insertions(+)
 create mode 100644 packages/kbn-generate-console-definitions/src/generate_query_params.test.ts

diff --git a/packages/kbn-generate-console-definitions/src/generate_query_params.test.ts b/packages/kbn-generate-console-definitions/src/generate_query_params.test.ts
new file mode 100644
index 0000000000000..556acac2e7546
--- /dev/null
+++ b/packages/kbn-generate-console-definitions/src/generate_query_params.test.ts
@@ -0,0 +1,327 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { SpecificationTypes } from './types';
+import { generateQueryParams } from './generate_query_params';
+
+describe('generateQueryParams', () => {
+  const mockRequestType: SpecificationTypes.Request = {
+    body: { kind: 'no_body' },
+    kind: 'request',
+    name: {
+      name: 'TestRequest',
+      namespace: 'test.namespace',
+    },
+    path: [],
+    query: [],
+    specLocation: '',
+  };
+
+  const getMockProperty = ({
+    propertyName,
+    typeName,
+    serverDefault,
+    type,
+  }: {
+    propertyName: string;
+    typeName?: SpecificationTypes.TypeName;
+    serverDefault?: SpecificationTypes.Property['serverDefault'];
+    type?: SpecificationTypes.ValueOf;
+  }): SpecificationTypes.Property => {
+    return {
+      description: 'Description',
+      name: propertyName,
+      required: false,
+      serverDefault: serverDefault ?? undefined,
+      type: type ?? {
+        kind: 'instance_of',
+        type: typeName ?? {
+          name: 'string',
+          namespace: '_builtins',
+        },
+      },
+    };
+  };
+
+  const mockSchema: SpecificationTypes.Model = {
+    endpoints: [],
+    types: [],
+  };
+
+  it('iterates over attachedBehaviours', () => {
+    const behaviour1: SpecificationTypes.Interface = {
+      kind: 'interface',
+      name: {
+        name: 'behaviour1',
+        namespace: 'test.namespace',
+      },
+      properties: [getMockProperty({ propertyName: 'property1' })],
+      specLocation: '',
+    };
+    const behaviour2: SpecificationTypes.Interface = {
+      kind: 'interface',
+      name: {
+        name: 'behaviour2',
+        namespace: 'test.namespace',
+      },
+      properties: [
+        getMockProperty({ propertyName: 'property2' }),
+        getMockProperty({ propertyName: 'property3' }),
+      ],
+      specLocation: '',
+    };
+    const schema: SpecificationTypes.Model = {
+      ...mockSchema,
+      types: [behaviour1, behaviour2],
+    };
+    const requestType: SpecificationTypes.Request = {
+      ...mockRequestType,
+      attachedBehaviors: ['behaviour1', 'behaviour2'],
+    };
+    const urlParams = generateQueryParams(requestType, schema);
+    expect(urlParams).toEqual({
+      property1: '',
+      property2: '',
+      property3: '',
+    });
+  });
+
+  it('iterates over query properties', () => {
+    const requestType = {
+      ...mockRequestType,
+      query: [
+        getMockProperty({ propertyName: 'property1' }),
+        getMockProperty({ propertyName: 'property2' }),
+      ],
+    };
+    const urlParams = generateQueryParams(requestType, mockSchema);
+    expect(urlParams).toEqual({
+      property1: '',
+      property2: '',
+    });
+  });
+
+  it('converts builtin types', () => {
+    const stringProperty = getMockProperty({
+      propertyName: 'stringProperty',
+      typeName: { name: 'string', namespace: '_builtins' },
+    });
+    const numberProperty = getMockProperty({
+      propertyName: 'numberProperty',
+      typeName: { name: 'number', namespace: '_builtins' },
+    });
+    const booleanProperty = getMockProperty({
+      propertyName: 'booleanProperty',
+      typeName: { name: 'boolean', namespace: '_builtins' },
+    });
+    const requestType = {
+      ...mockRequestType,
+      query: [stringProperty, numberProperty, booleanProperty],
+    };
+    const urlParams = generateQueryParams(requestType, mockSchema);
+    expect(urlParams).toEqual({
+      stringProperty: '',
+      numberProperty: '',
+      booleanProperty: '__flag__',
+    });
+  });
+
+  it('adds serverDefault value if any', () => {
+    const propertyWithDefault = getMockProperty({
+      propertyName: 'propertyWithDefault',
+      serverDefault: 'default',
+    });
+    const requestType = { ...mockRequestType, query: [propertyWithDefault] };
+    const urlParams = generateQueryParams(requestType, mockSchema);
+    expect(urlParams).toEqual({
+      propertyWithDefault: ['default'],
+    });
+  });
+
+  it('converts an enum property', () => {
+    const enumProperty = getMockProperty({
+      propertyName: 'enumProperty',
+      typeName: { name: 'EnumType', namespace: 'test.namespace' },
+    });
+    const enumType: SpecificationTypes.Enum = {
+      kind: 'enum',
+      members: [
+        {
+          name: 'enum1',
+        },
+        {
+          name: 'enum2',
+        },
+      ],
+      name: {
+        name: 'EnumType',
+        namespace: 'test.namespace',
+      },
+      specLocation: '',
+    };
+    const requestType = { ...mockRequestType, query: [enumProperty] };
+    const schema = { ...mockSchema, types: [enumType] };
+    const urlParams = generateQueryParams(requestType, schema);
+    expect(urlParams).toEqual({
+      enumProperty: ['enum1', 'enum2'],
+    });
+  });
+
+  it('converts a type alias', () => {
+    const typeAliasProperty = getMockProperty({
+      propertyName: 'typeAliasProperty',
+      typeName: {
+        name: 'SomeTypeAlias',
+        namespace: 'test.namespace',
+      },
+    });
+    const typeAliasType: SpecificationTypes.TypeAlias = {
+      kind: 'type_alias',
+      name: {
+        name: 'SomeTypeAlias',
+        namespace: 'test.namespace',
+      },
+      specLocation: '',
+      type: {
+        kind: 'instance_of',
+        type: {
+          name: 'integer',
+          namespace: '_types',
+        },
+      },
+    };
+    const requestType = { ...mockRequestType, query: [typeAliasProperty] };
+    const schema: SpecificationTypes.Model = { ...mockSchema, types: [typeAliasType] };
+    const urlParams = generateQueryParams(requestType, schema);
+    expect(urlParams).toEqual({
+      typeAliasProperty: '',
+    });
+  });
+
+  it('converts a literal_value to a string', () => {
+    const stringProperty = getMockProperty({
+      propertyName: 'stringProperty',
+      type: { kind: 'literal_value', value: 'stringValue' },
+    });
+    const numberProperty = getMockProperty({
+      propertyName: 'numberProperty',
+      type: { kind: 'literal_value', value: 14 },
+    });
+    const booleanProperty = getMockProperty({
+      propertyName: 'booleanProperty',
+      type: { kind: 'literal_value', value: true },
+    });
+    const requestType = {
+      ...mockRequestType,
+      query: [stringProperty, numberProperty, booleanProperty],
+    };
+    const urlParams = generateQueryParams(requestType, mockSchema);
+    expect(urlParams).toEqual({
+      stringProperty: ['stringValue'],
+      numberProperty: ['14'],
+      booleanProperty: ['true'],
+    });
+  });
+
+  describe('converts a union_of', () => {
+    it('flattens the array if one of the items is converted to an array', () => {
+      const enumType: SpecificationTypes.Enum = {
+        kind: 'enum',
+        members: [
+          {
+            name: 'enum1',
+          },
+          { name: 'enum2' },
+        ],
+        name: { name: 'EnumType', namespace: 'test.namespace' },
+        specLocation: '',
+      };
+      const unionProperty = getMockProperty({
+        propertyName: 'unionProperty',
+        type: {
+          kind: 'union_of',
+          items: [
+            {
+              kind: 'instance_of',
+              type: {
+                name: 'EnumType',
+                namespace: 'test.namespace',
+              },
+            },
+          ],
+        },
+      });
+      const requestType = { ...mockRequestType, query: [unionProperty] };
+      const schema: SpecificationTypes.Model = { ...mockSchema, types: [enumType] };
+      const urlParams = generateQueryParams(requestType, schema);
+      expect(urlParams).toEqual({
+        unionProperty: ['enum1', 'enum2'],
+      });
+    });
+
+    it('removes empty string from the array', () => {
+      const unionProperty = getMockProperty({
+        propertyName: 'unionProperty',
+        type: {
+          kind: 'union_of',
+          items: [
+            {
+              kind: 'instance_of',
+              type: {
+                name: 'string',
+                namespace: '_builtins',
+              },
+            },
+          ],
+        },
+      });
+      const requestType = { ...mockRequestType, query: [unionProperty] };
+      const urlParams = generateQueryParams(requestType, mockSchema);
+      expect(urlParams).toEqual({
+        unionProperty: [],
+      });
+    });
+
+    it('if one item is a boolean and others are empty, converts to a flag', () => {
+      const unionProperty = getMockProperty({
+        propertyName: 'unionProperty',
+        type: {
+          kind: 'union_of',
+          items: [
+            {
+              kind: 'instance_of',
+              type: {
+                name: 'string',
+                namespace: '_builtins',
+              },
+            },
+            {
+              kind: 'instance_of',
+              type: {
+                name: 'number',
+                namespace: '_builtins',
+              },
+            },
+            {
+              kind: 'instance_of',
+              type: {
+                name: 'boolean',
+                namespace: '_builtins',
+              },
+            },
+          ],
+        },
+      });
+      const requestType = { ...mockRequestType, query: [unionProperty] };
+      const urlParams = generateQueryParams(requestType, mockSchema);
+      expect(urlParams).toEqual({
+        unionProperty: '__flag__',
+      });
+    });
+  });
+});

From 801113e58b34302913694124331bd5b88cce3777 Mon Sep 17 00:00:00 2001
From: Yulia Cech <yulia.cech@elastic.co>
Date: Fri, 30 Jun 2023 18:45:36 +0200
Subject: [PATCH 6/8] [Console] Make sure no `undefined` values are added to
 the param values

---
 .../src/generate_query_params.test.ts         | 48 +++++++++++++++++++
 .../src/generate_query_params.ts              |  2 +
 2 files changed, 50 insertions(+)

diff --git a/packages/kbn-generate-console-definitions/src/generate_query_params.test.ts b/packages/kbn-generate-console-definitions/src/generate_query_params.test.ts
index 556acac2e7546..3d658ba60f174 100644
--- a/packages/kbn-generate-console-definitions/src/generate_query_params.test.ts
+++ b/packages/kbn-generate-console-definitions/src/generate_query_params.test.ts
@@ -8,6 +8,7 @@
 
 import { SpecificationTypes } from './types';
 import { generateQueryParams } from './generate_query_params';
+import { UrlParamValue } from './types/autocomplete_definition_types';
 
 describe('generateQueryParams', () => {
   const mockRequestType: SpecificationTypes.Request = {
@@ -323,5 +324,52 @@ describe('generateQueryParams', () => {
         unionProperty: '__flag__',
       });
     });
+
+    it('if one item is an unknown type, converts it to an empty string', () => {
+      const unionProperty = getMockProperty({
+        propertyName: 'unionProperty',
+        type: {
+          kind: 'union_of',
+          items: [
+            {
+              kind: 'literal_value',
+              value: 'test',
+            },
+            {
+              kind: 'instance_of',
+              type: {
+                name: 'UnknownType',
+                namespace: 'test.namespace',
+              },
+            },
+          ],
+        },
+      });
+
+      const requestType = { ...mockRequestType, query: [unionProperty] };
+      const urlParams = generateQueryParams(requestType, mockSchema);
+      // check that no `undefined` values are added
+      const value = urlParams.unionProperty as UrlParamValue[];
+      expect(value.length).toEqual(1);
+    });
+  });
+
+  it('converts an unknown type to an empty string', () => {
+    const unknownTypeProperty = getMockProperty({
+      propertyName: 'unknownTypeProperty',
+      type: {
+        kind: 'instance_of',
+        type: {
+          name: 'UnknownType',
+          namespace: 'test.namespace',
+        },
+      },
+    });
+
+    const requestType = { ...mockRequestType, query: [unknownTypeProperty] };
+    const urlParams = generateQueryParams(requestType, mockSchema);
+    expect(urlParams).toEqual({
+      unknownTypeProperty: '',
+    });
   });
 });
diff --git a/packages/kbn-generate-console-definitions/src/generate_query_params.ts b/packages/kbn-generate-console-definitions/src/generate_query_params.ts
index 99d59d2d318b0..6280bb77d3685 100644
--- a/packages/kbn-generate-console-definitions/src/generate_query_params.ts
+++ b/packages/kbn-generate-console-definitions/src/generate_query_params.ts
@@ -108,6 +108,7 @@ const convertValueOf = (
     return convertLiteralValue(valueOf);
   }
   // for query params we can ignore 'dictionary_of' and 'user_defined_value'
+  return '';
 };
 
 const convertInstanceOf = (
@@ -147,6 +148,7 @@ const convertInstanceOf = (
       }
     }
   }
+  return '';
 };
 
 const convertArrayOf = (

From 48dc57e29e18f00b7ec745269d2f9c5bb53cdeed Mon Sep 17 00:00:00 2001
From: Yulia Cech <yulia.cech@elastic.co>
Date: Wed, 5 Jul 2023 10:23:34 +0200
Subject: [PATCH 7/8] [Console] Add code review suggestions

---
 .../src/generate_query_params.ts                              | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/kbn-generate-console-definitions/src/generate_query_params.ts b/packages/kbn-generate-console-definitions/src/generate_query_params.ts
index 6280bb77d3685..5310e85d68936 100644
--- a/packages/kbn-generate-console-definitions/src/generate_query_params.ts
+++ b/packages/kbn-generate-console-definitions/src/generate_query_params.ts
@@ -133,7 +133,7 @@ const convertInstanceOf = (
       return booleanFlagString;
     } else {
       // if default value, convert to string and put in an array
-      return serverDefault ? [serverDefault + ''] : '';
+      return serverDefault ? [serverDefault.toString()] : '';
     }
   } else {
     // if it's a defined type, try to convert it
@@ -209,7 +209,7 @@ const convertUnionOf = (
 
 const convertLiteralValue = (type: SpecificationTypes.LiteralValue): UrlParamValue | undefined => {
   // convert the value to a string
-  return [type.value + ''];
+  return [type.value.toString()];
 };
 
 const convertEnum = (enumDefinition: SpecificationTypes.Enum): UrlParamValue => {

From 7025979755f5df219a9ddd4c069b2e653aae9070 Mon Sep 17 00:00:00 2001
From: Yulia Cech <yulia.cech@elastic.co>
Date: Wed, 5 Jul 2023 11:36:58 +0200
Subject: [PATCH 8/8] [Console] Add readme

---
 packages/kbn-generate-console-definitions/README.md | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/packages/kbn-generate-console-definitions/README.md b/packages/kbn-generate-console-definitions/README.md
index 71596b6fdf0bc..75d43fd7c493e 100644
--- a/packages/kbn-generate-console-definitions/README.md
+++ b/packages/kbn-generate-console-definitions/README.md
@@ -1,3 +1,10 @@
-# @kbn/generate-console-definitions
+# Generate console definitions
+This package is a script to generate definitions used in Console to display autocomplete suggestions. The script is 
+a new implementation of `kbn-spec-to-console` package: The old script uses [JSON specs](https://github.com/elastic/elasticsearch/tree/main/rest-api-spec) from the Elasticsearch repo as the source, whereas this script uses the Elasticsearch specification [repo](https://github.com/elastic/elasticsearch-specification) as the source.
+
+## Instructions
+1. Checkout the Elasticsearch specification [repo](https://github.com/elastic/elasticsearch-specification).
+2. Run the command `node scripts/generate_console_definitions.js --source <ES_SPECIFICATION_REPO> --emptyDest`
+  This command will use the folder `<ES_SPECIFICATION_REPO>` as the source and the constant [`AUTOCOMPLETE_DEFINITIONS_FOLDER`](https://github.com/elastic/kibana/blob/main/src/plugins/console/common/constants/autocomplete_definitions.ts) as the destination. Based on the value of the constant, the autocomplete definitions will be generated in the folder `<KIBANA_REPO>/src/plugins/server/lib/spec_definitions/json/generated`. Using the flag `--emptyDest` will remove any existing files in the destination folder. 
+3. It's possible to generate the definitions into a different folder. For that pass an option to the command `--dest <DEFINITIONS_FOLDER>` and also update the constant [`AUTOCOMPLETE_DEFINITIONS_FOLDER`](https://github.com/elastic/kibana/blob/main/src/plugins/console/common/constants/autocomplete_definitions.ts) so that the Console server will load the definitions from this folder. 
 
-Empty package generated by @kbn/generate