Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Extract OpenAPI codegen to a package #166269

Merged
merged 2 commits into from
Sep 25, 2023

Conversation

xcrzx
Copy link
Contributor

@xcrzx xcrzx commented Sep 12, 2023

Related to: https://github.com/elastic/security-team/issues/7583

Summary

Extract the OpenAPI codegen into a separate package @kbn/openapi-generator. The code generator script is now configurable.

README

This code generator could be used to generate runtime types, documentation, server stub implementations, clients, and much more given OpenAPI specification.

Getting started

To start with code generation you should have OpenAPI specification describing your API endpoint request and response schemas along with common types used in your API. The code generation script supports OpenAPI 3.1.0, refer to https://swagger.io/specification/ for more details.

OpenAPI specification should be in YAML format and have .schema.yaml extension. Here's a simple example of OpenAPI specification:

openapi: 3.0.0
info:
  title: Install Prebuilt Rules API endpoint
  version: 2023-10-31
paths:
  /api/detection_engine/rules/prepackaged:
    put:
      operationId: InstallPrebuiltRules
      x-codegen-enabled: true
      summary: Installs all Elastic prebuilt rules and timelines
      tags:
        - Prebuilt Rules API
      responses:
        200:
          description: Indicates a successful call
          content:
            application/json:
              schema:
                type: object
                properties:
                  rules_installed:
                    type: integer
                    description: The number of rules installed
                    minimum: 0
                  rules_updated:
                    type: integer
                    description: The number of rules updated
                    minimum: 0
                  timelines_installed:
                    type: integer
                    description: The number of timelines installed
                    minimum: 0
                  timelines_updated:
                    type: integer
                    description: The number of timelines updated
                    minimum: 0
                required:
                  - rules_installed
                  - rules_updated
                  - timelines_installed
                  - timelines_updated

Put it anywhere in your plugin, the code generation script will traverse the whole plugin directory and find all .schema.yaml files.

Then to generate code run the following command:

node scripts/generate_openapi --rootDir ./x-pack/plugins/security_solution

Generator command output

By default it uses the zod_operation_schema template which produces runtime types for request and response schemas described in OpenAPI specification. The generated code will be placed adjacent to the .schema.yaml file and will have .gen.ts extension.

Example of generated code:

import { z } from 'zod';

/*
 * NOTICE: Do not edit this file manually.
 * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
 */

export type InstallPrebuiltRulesResponse = z.infer<typeof InstallPrebuiltRulesResponse>;
export const InstallPrebuiltRulesResponse = z.object({
  /**
   * The number of rules installed
   */
  rules_installed: z.number().int().min(0),
  /**
   * The number of rules updated
   */
  rules_updated: z.number().int().min(0),
  /**
   * The number of timelines installed
   */
  timelines_installed: z.number().int().min(0),
  /**
   * The number of timelines updated
   */
  timelines_updated: z.number().int().min(0),
});

Programmatic API

Alternatively, you can use the code generator programmatically. You can create a script file and run it with node command. This could be useful if you want to set up code generation in your CI pipeline. Here's an example of such script:

require('../../../../../src/setup_node_env');
const { generate } = require('@kbn/openapi-generator');
const { resolve } = require('path');

const SECURITY_SOLUTION_ROOT = resolve(__dirname, '../..');

generate({
  rootDir: SECURITY_SOLUTION_ROOT, // Path to the plugin root directory
  sourceGlob: './**/*.schema.yaml', // Glob pattern to find OpenAPI specification files
  templateName: 'zod_operation_schema', // Name of the template to use
});

CI integration

To make sure that generated code is always in sync with its OpenAPI specification it is recommended to add a command to your CI pipeline that will run code generation on every pull request and commit the changes if there are any.

First, create a script that will run code generation and commit the changes. See .buildkite/scripts/steps/code_generation/security_solution_codegen.sh for an example:

#!/usr/bin/env bash

set -euo pipefail

source .buildkite/scripts/common/util.sh

.buildkite/scripts/bootstrap.sh

echo --- Security Solution OpenAPI Code Generation

(cd x-pack/plugins/security_solution && yarn openapi:generate)
check_for_changed_files "yarn openapi:generate" true

This script sets up the minimal environment required fro code generation and runs the code generation script. Then it checks if there are any changes and commits them if there are any using the check_for_changed_files function.

Then add the code generation script to your plugin build pipeline. Open your plugin build pipeline, for example .buildkite/pipelines/pull_request/security_solution.yml, and add the following command to the steps list adjusting the path to your code generation script:

  - command: .buildkite/scripts/steps/code_generation/security_solution_codegen.sh
    label: 'Security Solution OpenAPI codegen'
    agents:
      queue: n2-2-spot
    timeout_in_minutes: 60
    parallelism: 1

Now on every pull request the code generation script will run and commit the changes if there are any.

OpenAPI Schema

The code generator supports the OpenAPI definitions described in the request, response, and component sections of the document.

For every API operation (GET, POST, etc) it is required to specify the operationId field. This field is used to generate the name of the generated types. For example, if the operationId is InstallPrebuiltRules then the generated types will be named InstallPrebuiltRulesResponse and InstallPrebuiltRulesRequest. If the operationId is not specified then the code generation will throw an error.

The x-codegen-enabled field is used to enable or disable code generation for the operation. If it is not specified then code generation is disabled by default. This field could be also used to disable code generation of common components described in the components section of the OpenAPI specification.

Keep in mind that disabling code generation for common components that are referenced by external OpenAPI specifications could lead to errors during code generation.

Schema files organization

It is recommended to limit the number of operations and components described in a single OpenAPI specification file. Having one HTTP operation in a single file will make it easier to maintain and will keep the generated artifacts granular for ease of reuse and better tree shaking. You can have as many OpenAPI specification files as you want.

Common components

It is common to have shared types that are used in multiple API operations. To avoid code duplication you can define common components in the components section of the OpenAPI specification and put them in a separate file. Then you can reference these components in the parameters and responses sections of the API operations.

Here's an example of the schema that references common components:

openapi: 3.0.0
info:
  title: Delete Rule API endpoint
  version: 2023-10-31
paths:
  /api/detection_engine/rules:
    delete:
      operationId: DeleteRule
      description: Deletes a single rule using the `rule_id` or `id` field.
      parameters:
        - name: id
          in: query
          required: false
          description: The rule's `id` value.
          schema:
            $ref: '../../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleSignatureId'
        - name: rule_id
          in: query
          required: false
          description: The rule's `rule_id` value.
          schema:
            $ref: '../../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleObjectId'
      responses:
        200:
          description: Indicates a successful call.
          content:
            application/json:
              schema:
                $ref: '../../../model/rule_schema/rule_schemas.schema.yaml#/components/schemas/RuleResponse'

@xcrzx xcrzx added release_note:skip Skip the PR/issue when compiling release notes Team:Detections and Resp Security Detection Response Team Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. Team:Detection Rule Management Security Detection Rule Management Team v8.11.0 labels Sep 12, 2023
@xcrzx xcrzx self-assigned this Sep 12, 2023
@xcrzx xcrzx force-pushed the openapi-generator-package branch 4 times, most recently from d323bb7 to 5f0b8df Compare September 13, 2023 15:14
@xcrzx xcrzx marked this pull request as ready for review September 13, 2023 15:30
@xcrzx xcrzx requested review from a team as code owners September 13, 2023 15:30
@elasticmachine
Copy link
Contributor

Pinging @elastic/security-detections-response (Team:Detections and Resp)

@elasticmachine
Copy link
Contributor

Pinging @elastic/security-solution (Team: SecuritySolution)

@banderror banderror requested review from spong and removed request for dplumlee September 18, 2023 10:37
@spong
Copy link
Member

spong commented Sep 20, 2023

Haven't had a chance to take a look at this yet @xcrzx, but hoping to give it a test run by integrating w/ the elastic_assistant plugin to see how generation goes. Will provide some feedback here in the next couple of days 👍

Comment on lines +1 to +4
# OpenAPI Code Generator for Kibana

This code generator could be used to generate runtime types, documentation, server stub implementations, clients, and much more given OpenAPI specification.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for including the detailed description here in the README as well!

WRT wider adoption and use, should we surface as a documentation set on docs.elastic.dev, or have an entry in the Kibana Developer Guide?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's a good idea 👍 I've also been thinking about making a broader announcement, like sending an email about the codegen features to attract more early adopters. But starting with documentation is definitely a solid first step.

})
.option('sourceGlob', {
describe: 'Elasticsearch target',
default: './**/*.schema.yaml',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't read close enough the first pass through and created an assistant.schema.yml and it wasn't getting picked up since it wasn't .yaml. After some searching it seems yaml is the defacto/preferred extension even though we have a lot of .yml files floating around the Kibana repo. nit here would be maybe also support .yml, or print an info/warning message around 🕵️‍♀️ Found 0 schemas, parsing, but this is entirely user error, so feel free to do nothing here 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding .yml support should be pretty straightforward. I can tackle that in subsequent PRs, since work on the package is still ongoing.

Comment on lines +45 to +46
// eslint-disable-next-line no-console
console.error(err);
Copy link
Member

@spong spong Sep 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% on usage here, but there is the ToolingLog package for logger usage within Kibana tooling, e.g. from the resolver generator script (which also uses console for some output, so IDK...):

const logger = new ToolingLog({
level: 'info',
writeTo: process.stdout,
});
const toolingLogOptions = { log: logger };

edit: Probably because of the logger prefix vs prettyprint of console would be my first guess here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't know that we have a package for logging. Thanks for bringing it up 👍 I'll look into replacing the console.logs with it.

@@ -20,30 +20,30 @@ export const GetPrebuiltRulesAndTimelinesStatusResponse = z
/**
* The total number of custom rules
*/
rules_custom_installed: z.number().min(0),
rules_custom_installed: z.number().int().min(0),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What change ended up causing these int()'s to be added here and in the endpoint files generated below? Not seeing a change to a schema.yaml, so looks like it was the {{~#*inline "type_integer"~}} change over in zod_schema_item.handlebars?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While moving templates around, I noticed that we were missing .int() for integers, so I decided to add it right away. I also made some other minor clean-ups in the templates but they didn't affect generated artifacts.

Copy link
Member

@spong spong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked out, tested locally, and LGTM!

Thank you for all your efforts here in not only setting up our OpenAPI code generation, but taking the time to generalize it so others can start leveraging all this goodness as well! Left a few nits, but everything worked 👍 when testing locally, and code changes look good as well, so no changes necessary 🙂

Co-authored-by: Garrett Spong <spong@users.noreply.github.com>
@kibana-ci
Copy link
Collaborator

💚 Build Succeeded

Metrics [docs]

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
@kbn/openapi-generator - 7 +7

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
securitySolution 12.8MB 12.8MB +168.0B
Unknown metric groups

API count

id before after diff
@kbn/openapi-generator - 7 +7

ESLint disabled in files

id before after diff
@kbn/openapi-generator - 1 +1
securitySolution 67 66 -1
total -0

ESLint disabled line counts

id before after diff
@kbn/openapi-generator - 2 +2
securitySolution 455 454 -1
total +1

Total ESLint disabled count

id before after diff
@kbn/openapi-generator - 3 +3
securitySolution 522 520 -2
total +1

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

cc @xcrzx

Copy link
Contributor

@tomsonpl tomsonpl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DW lgtm 👍 thanks

@xcrzx xcrzx merged commit 38e6b76 into elastic:main Sep 25, 2023
28 checks passed
@kibanamachine kibanamachine added the backport:skip This commit does not require backporting label Sep 25, 2023
@xcrzx xcrzx deleted the openapi-generator-package branch September 25, 2023 08:52
spong added a commit that referenced this pull request Jan 11, 2024
…ature registration (#174317)

## Summary

Resolves #172509

Adds ability to register feature capabilities through the assistant
server so they no longer need to be plumbed through the
`ElasticAssistantProvider`, which now also makes them available server
side.

Adds new `/internal/elastic_assistant/capabilities` route and
`useCapabilities()` UI hook for fetching capabilities.

### OpenAPI Codegen

Implemented using the new OpenAPI codegen and bundle packages:
* Includes OpenAPI codegen script and CI action as detailed in:
#166269
* Includes OpenAPI docs bundling script as detailed in:
#171526

To run codegen/bundling locally, cd to
`x-pack/plugins/elastic_assistant/` and run any of the following
commands:

```bash
yarn openapi:generate
yarn openapi:generate:debug
yarn openapi:bundle
```

> [!NOTE]
> At the moment `yarn openapi:bundle` will output an empty bundled
schema since `get_capabilities_route` is an internal route, this is to
be expected. Also, if you don't see the file in your IDE, it's probably
because `target` directories are ignored, so you may need to manually
find/open the bundled schema at it's target location:
`/x-pack/plugins/elastic_assistant/target/openapi/elastic_assistant.bundled.schema.yaml`

### Registering Capabilities 

To register a capability on plugin start, add the following in the
consuming plugin's `start()`:

```ts
plugins.elasticAssistant.registerFeatures(APP_UI_ID, {
  assistantModelEvaluation: config.experimentalFeatures.assistantModelEvaluation,
  assistantStreamingEnabled: config.experimentalFeatures.assistantStreamingEnabled,
});
```

### Declaring Feature Capabilities
Feature capabilities are declared in
`x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts`:

```ts
/**
 * Interfaces for features available to the elastic assistant
 */
export type AssistantFeatures = { [K in keyof typeof assistantFeatures]: boolean };

export const assistantFeatures = Object.freeze({
  assistantModelEvaluation: false,
  assistantStreamingEnabled: false,
});
```
### Using Capabilities Client Side
And can be fetched client side using the `useCapabilities()` hook ala:

```ts
// Fetch assistant capabilities
const { data: capabilities } = useCapabilities({ http, toasts });
const { assistantModelEvaluation: modelEvaluatorEnabled, assistantStreamingEnabled } = capabilities ?? assistantFeatures;
```

### Using Capabilities Server Side
Or server side within a route (or elsewhere) via the `assistantContext`:

```ts
const assistantContext = await context.elasticAssistant;
const pluginName = getPluginNameFromRequest({ request, logger });
const registeredFeatures = assistantContext.getRegisteredFeatures(pluginName);
if (!registeredFeatures.assistantModelEvaluation) {
  return response.notFound();
}
```

> [!NOTE]
> Note, just as with [registering arbitrary
tools](#172234), features are
registered for a specific plugin, where the plugin name that corresponds
to your application is defined in the `x-kbn-context` header of requests
made from your application, which may be different than your plugin's
registered `APP_ID`.

Perhaps this separation of concerns from one plugin to another isn't
necessary, but it was easy to add matching the behavior of registering
arbitrary tools. We can remove this granularity in favor of global
features if desired.


### Test Steps

* Verify `/internal/elastic_assistant/capabilities` route is called on
security solution page load in dev tools, and that by default the
`Evaluation` UI in setting does is not displayed and `404`'s if manually
called.
* Set the below experimental feature flag in your `kibana.dev.yml` and
observe the feature being enabled by inspecting the capabilities api
response, and that the evaluation feature becomes available:
```
xpack.securitySolution.enableExperimental: [ 'assistantModelEvaluation']
```
* Run the `yarn openapi:*` codegen scripts above and ensure they execute
as expected (code is generated/bundled)

### Checklist

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [X] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
eokoneyo pushed a commit to eokoneyo/kibana that referenced this pull request Jan 12, 2024
…ature registration (elastic#174317)

## Summary

Resolves elastic#172509

Adds ability to register feature capabilities through the assistant
server so they no longer need to be plumbed through the
`ElasticAssistantProvider`, which now also makes them available server
side.

Adds new `/internal/elastic_assistant/capabilities` route and
`useCapabilities()` UI hook for fetching capabilities.

### OpenAPI Codegen

Implemented using the new OpenAPI codegen and bundle packages:
* Includes OpenAPI codegen script and CI action as detailed in:
elastic#166269
* Includes OpenAPI docs bundling script as detailed in:
elastic#171526

To run codegen/bundling locally, cd to
`x-pack/plugins/elastic_assistant/` and run any of the following
commands:

```bash
yarn openapi:generate
yarn openapi:generate:debug
yarn openapi:bundle
```

> [!NOTE]
> At the moment `yarn openapi:bundle` will output an empty bundled
schema since `get_capabilities_route` is an internal route, this is to
be expected. Also, if you don't see the file in your IDE, it's probably
because `target` directories are ignored, so you may need to manually
find/open the bundled schema at it's target location:
`/x-pack/plugins/elastic_assistant/target/openapi/elastic_assistant.bundled.schema.yaml`

### Registering Capabilities 

To register a capability on plugin start, add the following in the
consuming plugin's `start()`:

```ts
plugins.elasticAssistant.registerFeatures(APP_UI_ID, {
  assistantModelEvaluation: config.experimentalFeatures.assistantModelEvaluation,
  assistantStreamingEnabled: config.experimentalFeatures.assistantStreamingEnabled,
});
```

### Declaring Feature Capabilities
Feature capabilities are declared in
`x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts`:

```ts
/**
 * Interfaces for features available to the elastic assistant
 */
export type AssistantFeatures = { [K in keyof typeof assistantFeatures]: boolean };

export const assistantFeatures = Object.freeze({
  assistantModelEvaluation: false,
  assistantStreamingEnabled: false,
});
```
### Using Capabilities Client Side
And can be fetched client side using the `useCapabilities()` hook ala:

```ts
// Fetch assistant capabilities
const { data: capabilities } = useCapabilities({ http, toasts });
const { assistantModelEvaluation: modelEvaluatorEnabled, assistantStreamingEnabled } = capabilities ?? assistantFeatures;
```

### Using Capabilities Server Side
Or server side within a route (or elsewhere) via the `assistantContext`:

```ts
const assistantContext = await context.elasticAssistant;
const pluginName = getPluginNameFromRequest({ request, logger });
const registeredFeatures = assistantContext.getRegisteredFeatures(pluginName);
if (!registeredFeatures.assistantModelEvaluation) {
  return response.notFound();
}
```

> [!NOTE]
> Note, just as with [registering arbitrary
tools](elastic#172234), features are
registered for a specific plugin, where the plugin name that corresponds
to your application is defined in the `x-kbn-context` header of requests
made from your application, which may be different than your plugin's
registered `APP_ID`.

Perhaps this separation of concerns from one plugin to another isn't
necessary, but it was easy to add matching the behavior of registering
arbitrary tools. We can remove this granularity in favor of global
features if desired.


### Test Steps

* Verify `/internal/elastic_assistant/capabilities` route is called on
security solution page load in dev tools, and that by default the
`Evaluation` UI in setting does is not displayed and `404`'s if manually
called.
* Set the below experimental feature flag in your `kibana.dev.yml` and
observe the feature being enabled by inspecting the capabilities api
response, and that the evaluation feature becomes available:
```
xpack.securitySolution.enableExperimental: [ 'assistantModelEvaluation']
```
* Run the `yarn openapi:*` codegen scripts above and ensure they execute
as expected (code is generated/bundled)

### Checklist

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [X] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
semd pushed a commit to semd/kibana that referenced this pull request Jan 12, 2024
…ature registration (elastic#174317)

## Summary

Resolves elastic#172509

Adds ability to register feature capabilities through the assistant
server so they no longer need to be plumbed through the
`ElasticAssistantProvider`, which now also makes them available server
side.

Adds new `/internal/elastic_assistant/capabilities` route and
`useCapabilities()` UI hook for fetching capabilities.

### OpenAPI Codegen

Implemented using the new OpenAPI codegen and bundle packages:
* Includes OpenAPI codegen script and CI action as detailed in:
elastic#166269
* Includes OpenAPI docs bundling script as detailed in:
elastic#171526

To run codegen/bundling locally, cd to
`x-pack/plugins/elastic_assistant/` and run any of the following
commands:

```bash
yarn openapi:generate
yarn openapi:generate:debug
yarn openapi:bundle
```

> [!NOTE]
> At the moment `yarn openapi:bundle` will output an empty bundled
schema since `get_capabilities_route` is an internal route, this is to
be expected. Also, if you don't see the file in your IDE, it's probably
because `target` directories are ignored, so you may need to manually
find/open the bundled schema at it's target location:
`/x-pack/plugins/elastic_assistant/target/openapi/elastic_assistant.bundled.schema.yaml`

### Registering Capabilities 

To register a capability on plugin start, add the following in the
consuming plugin's `start()`:

```ts
plugins.elasticAssistant.registerFeatures(APP_UI_ID, {
  assistantModelEvaluation: config.experimentalFeatures.assistantModelEvaluation,
  assistantStreamingEnabled: config.experimentalFeatures.assistantStreamingEnabled,
});
```

### Declaring Feature Capabilities
Feature capabilities are declared in
`x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts`:

```ts
/**
 * Interfaces for features available to the elastic assistant
 */
export type AssistantFeatures = { [K in keyof typeof assistantFeatures]: boolean };

export const assistantFeatures = Object.freeze({
  assistantModelEvaluation: false,
  assistantStreamingEnabled: false,
});
```
### Using Capabilities Client Side
And can be fetched client side using the `useCapabilities()` hook ala:

```ts
// Fetch assistant capabilities
const { data: capabilities } = useCapabilities({ http, toasts });
const { assistantModelEvaluation: modelEvaluatorEnabled, assistantStreamingEnabled } = capabilities ?? assistantFeatures;
```

### Using Capabilities Server Side
Or server side within a route (or elsewhere) via the `assistantContext`:

```ts
const assistantContext = await context.elasticAssistant;
const pluginName = getPluginNameFromRequest({ request, logger });
const registeredFeatures = assistantContext.getRegisteredFeatures(pluginName);
if (!registeredFeatures.assistantModelEvaluation) {
  return response.notFound();
}
```

> [!NOTE]
> Note, just as with [registering arbitrary
tools](elastic#172234), features are
registered for a specific plugin, where the plugin name that corresponds
to your application is defined in the `x-kbn-context` header of requests
made from your application, which may be different than your plugin's
registered `APP_ID`.

Perhaps this separation of concerns from one plugin to another isn't
necessary, but it was easy to add matching the behavior of registering
arbitrary tools. We can remove this granularity in favor of global
features if desired.


### Test Steps

* Verify `/internal/elastic_assistant/capabilities` route is called on
security solution page load in dev tools, and that by default the
`Evaluation` UI in setting does is not displayed and `404`'s if manually
called.
* Set the below experimental feature flag in your `kibana.dev.yml` and
observe the feature being enabled by inspecting the capabilities api
response, and that the evaluation feature becomes available:
```
xpack.securitySolution.enableExperimental: [ 'assistantModelEvaluation']
```
* Run the `yarn openapi:*` codegen scripts above and ensure they execute
as expected (code is generated/bundled)

### Checklist

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [X] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
CoenWarmer pushed a commit to CoenWarmer/kibana that referenced this pull request Feb 15, 2024
…ature registration (elastic#174317)

## Summary

Resolves elastic#172509

Adds ability to register feature capabilities through the assistant
server so they no longer need to be plumbed through the
`ElasticAssistantProvider`, which now also makes them available server
side.

Adds new `/internal/elastic_assistant/capabilities` route and
`useCapabilities()` UI hook for fetching capabilities.

### OpenAPI Codegen

Implemented using the new OpenAPI codegen and bundle packages:
* Includes OpenAPI codegen script and CI action as detailed in:
elastic#166269
* Includes OpenAPI docs bundling script as detailed in:
elastic#171526

To run codegen/bundling locally, cd to
`x-pack/plugins/elastic_assistant/` and run any of the following
commands:

```bash
yarn openapi:generate
yarn openapi:generate:debug
yarn openapi:bundle
```

> [!NOTE]
> At the moment `yarn openapi:bundle` will output an empty bundled
schema since `get_capabilities_route` is an internal route, this is to
be expected. Also, if you don't see the file in your IDE, it's probably
because `target` directories are ignored, so you may need to manually
find/open the bundled schema at it's target location:
`/x-pack/plugins/elastic_assistant/target/openapi/elastic_assistant.bundled.schema.yaml`

### Registering Capabilities 

To register a capability on plugin start, add the following in the
consuming plugin's `start()`:

```ts
plugins.elasticAssistant.registerFeatures(APP_UI_ID, {
  assistantModelEvaluation: config.experimentalFeatures.assistantModelEvaluation,
  assistantStreamingEnabled: config.experimentalFeatures.assistantStreamingEnabled,
});
```

### Declaring Feature Capabilities
Feature capabilities are declared in
`x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts`:

```ts
/**
 * Interfaces for features available to the elastic assistant
 */
export type AssistantFeatures = { [K in keyof typeof assistantFeatures]: boolean };

export const assistantFeatures = Object.freeze({
  assistantModelEvaluation: false,
  assistantStreamingEnabled: false,
});
```
### Using Capabilities Client Side
And can be fetched client side using the `useCapabilities()` hook ala:

```ts
// Fetch assistant capabilities
const { data: capabilities } = useCapabilities({ http, toasts });
const { assistantModelEvaluation: modelEvaluatorEnabled, assistantStreamingEnabled } = capabilities ?? assistantFeatures;
```

### Using Capabilities Server Side
Or server side within a route (or elsewhere) via the `assistantContext`:

```ts
const assistantContext = await context.elasticAssistant;
const pluginName = getPluginNameFromRequest({ request, logger });
const registeredFeatures = assistantContext.getRegisteredFeatures(pluginName);
if (!registeredFeatures.assistantModelEvaluation) {
  return response.notFound();
}
```

> [!NOTE]
> Note, just as with [registering arbitrary
tools](elastic#172234), features are
registered for a specific plugin, where the plugin name that corresponds
to your application is defined in the `x-kbn-context` header of requests
made from your application, which may be different than your plugin's
registered `APP_ID`.

Perhaps this separation of concerns from one plugin to another isn't
necessary, but it was easy to add matching the behavior of registering
arbitrary tools. We can remove this granularity in favor of global
features if desired.


### Test Steps

* Verify `/internal/elastic_assistant/capabilities` route is called on
security solution page load in dev tools, and that by default the
`Evaluation` UI in setting does is not displayed and `404`'s if manually
called.
* Set the below experimental feature flag in your `kibana.dev.yml` and
observe the feature being enabled by inspecting the capabilities api
response, and that the evaluation feature becomes available:
```
xpack.securitySolution.enableExperimental: [ 'assistantModelEvaluation']
```
* Run the `yarn openapi:*` codegen scripts above and ensure they execute
as expected (code is generated/bundled)

### Checklist

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [X] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:skip This commit does not require backporting release_note:skip Skip the PR/issue when compiling release notes Team:Detection Rule Management Security Detection Rule Management Team Team:Detections and Resp Security Detection Response Team Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. v8.11.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants