Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions oas_docs/output/kibana.serverless.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@ paths:
allow_multiple_system_actions:
description: Indicates whether multiple instances of the same system action connector can be used in a single rule.
type: boolean
description:
description: Description of the connector type.
type: string
enabled:
description: Indicates whether the connector is enabled.
type: boolean
Expand All @@ -371,6 +374,9 @@ paths:
is_deprecated:
description: Indicates whether the connector type is deprecated.
type: boolean
is_experimental:
description: When true, the connector type is a technical preview in the UI.
type: boolean
is_system_action_type:
description: Indicates whether the action is a system action.
type: boolean
Expand Down
6 changes: 6 additions & 0 deletions oas_docs/output/kibana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,9 @@ paths:
allow_multiple_system_actions:
description: Indicates whether multiple instances of the same system action connector can be used in a single rule.
type: boolean
description:
description: Description of the connector type.
type: string
enabled:
description: Indicates whether the connector is enabled.
type: boolean
Expand All @@ -441,6 +444,9 @@ paths:
is_deprecated:
description: Indicates whether the connector type is deprecated.
type: boolean
is_experimental:
description: When true, the connector type is a technical preview in the UI.
type: boolean
is_system_action_type:
description: Indicates whether the action is a system action.
type: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export interface ActionType {
subFeature?: SubFeature;
isDeprecated: boolean;
allowMultipleSystemActions?: boolean;
description?: string;
isExperimental?: boolean;
}

export type ConnectorUserAuthStatus = 'connected' | 'not_connected' | 'not_applicable';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export { AddMessageVariables } from './src/add_message_variables';

export * from './src/common/formatters';
export * from './src/common/hooks';
export * from './src/common/utils';
export { AlertsSearchBar } from './src/alerts_search_bar';
export type { AlertsSearchBarProps } from './src/alerts_search_bar/types';

Expand Down
3 changes: 3 additions & 0 deletions src/platform/packages/shared/kbn-alerts-ui-shared/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependsOn:
- '@kbn/triggers-actions-ui-types'
- '@kbn/alerting-types'
- '@kbn/actions-types'
- '@kbn/connector-specs'
- '@kbn/data-views-plugin'
- '@kbn/unified-search-plugin'
- '@kbn/es-query'
Expand All @@ -41,6 +42,7 @@ dependsOn:
- '@kbn/core-notifications-browser-mocks'
- '@kbn/shared-ux-table-persist'
- '@kbn/presentation-publishing'
- '@kbn/response-ops-form-generator'
- '@kbn/response-ops-rules-apis'
- '@kbn/controls-constants'
- '@kbn/controls-schemas'
Expand All @@ -51,6 +53,7 @@ dependsOn:
- '@kbn/licensing-plugin'
- '@kbn/licensing-types'
- '@kbn/test-jest-helpers'
- '@kbn/zod'
tags:
- shared-browser
- package
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export type {
ConnectorSpecResponse,
ConnectorSpecWireResponse,
} from './transform_connector_spec_response';
export { transformConnectorSpecResponse } from './transform_connector_spec_response';
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { transformConnectorSpecResponse } from './transform_connector_spec_response';

describe('transformConnectorSpecResponse', () => {
it('maps snake_case metadata to ConnectorMetadata', () => {
const result = transformConnectorSpecResponse({
metadata: {
id: '.my-connector',
display_name: 'My connector',
description: 'Does things',
minimum_license: 'basic',
supported_feature_ids: ['alerting'],
icon: 'logoElastic',
docs_url: 'https://example.com/docs',
is_technical_preview: true,
},
schema: { type: 'object', properties: {} },
});

expect(result.metadata).toEqual({
id: '.my-connector',
displayName: 'My connector',
description: 'Does things',
minimumLicense: 'basic',
supportedFeatureIds: ['alerting'],
icon: 'logoElastic',
docsUrl: 'https://example.com/docs',
isTechnicalPreview: true,
});
expect(result.schema).toEqual({ type: 'object', properties: {} });
});

it('omits optional metadata fields when absent on the wire', () => {
const result = transformConnectorSpecResponse({
metadata: {
id: 'c',
display_name: 'C',
description: 'D',
minimum_license: 'gold',
supported_feature_ids: ['cases'],
},
schema: {},
});

expect(result.metadata).toEqual({
id: 'c',
displayName: 'C',
description: 'D',
minimumLicense: 'gold',
supportedFeatureIds: ['cases'],
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { ConnectorMetadata } from '@kbn/connector-specs';

/**
* Wire JSON from GET /internal/actions/connector_types/{id}/spec
* (snake_case metadata; matches server `GetConnectorSpecResponseV1`).
*/
export interface ConnectorSpecWireResponse {
metadata: {
id: string;
display_name: string;
description: string;
minimum_license: string;
supported_feature_ids: string[];
icon?: string;
docs_url?: string;
is_technical_preview?: boolean;
};
schema: Record<string, unknown>;
}

/** Client-side connector spec after normalising API casing. */
export interface ConnectorSpecResponse {
metadata: ConnectorMetadata;
schema: Record<string, unknown>;
}

export function transformConnectorSpecResponse(
wire: ConnectorSpecWireResponse
): ConnectorSpecResponse {
const {
display_name: displayName,
minimum_license: minimumLicense,
supported_feature_ids: supportedFeatureIds,
docs_url: docsUrl,
is_technical_preview: isTechnicalPreview,
icon,
description,
id,
} = wire.metadata;

return {
metadata: {
id,
displayName,
description,
minimumLicense: minimumLicense as ConnectorMetadata['minimumLicense'],
supportedFeatureIds: supportedFeatureIds as ConnectorMetadata['supportedFeatureIds'],
...(icon !== undefined ? { icon } : {}),
...(docsUrl !== undefined ? { docsUrl } : {}),
...(isTechnicalPreview !== undefined ? { isTechnicalPreview } : {}),
},
schema: wire.schema,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ describe('transformConnectorTypesResponse', () => {
sub_feature: 'endpointSecurity',
is_deprecated: false,
allow_multiple_system_actions: true,
description: 'Card subtitle from list API',
is_experimental: true,
},
{
id: 'actionType2Id',
Expand Down Expand Up @@ -51,6 +53,8 @@ describe('transformConnectorTypesResponse', () => {
subFeature: 'endpointSecurity',
isDeprecated: false,
allowMultipleSystemActions: true,
description: 'Card subtitle from list API',
isExperimental: true,
},
{
id: 'actionType2Id',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const transformConnectorType: RewriteRequestCase<ActionType> = ({
sub_feature: subFeature,
is_deprecated: isDeprecated,
allow_multiple_system_actions: allowMultipleSystemActions,
description,
is_experimental: isExperimental,
...res
}: AsApiContract<ActionType>) => ({
enabledInConfig,
Expand All @@ -28,6 +30,8 @@ const transformConnectorType: RewriteRequestCase<ActionType> = ({
subFeature,
isDeprecated,
allowMultipleSystemActions,
description,
isExperimental,
...res,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export * from './fetch_alerts_index_names';
export * from './fetch_connector';
export * from './fetch_connectors';
export * from './fetch_connector_types';
export * from './fetch_connector_spec';
export * from './fetch_rule_type_alert_fields';
export * from './fetch_ui_health_status';
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export * from './use_load_alerting_framework_health';
export * from './use_get_rule_types_permissions';
export * from './use_load_ui_health';
export * from './use_fetch_unified_alerts_fields';
export * from './use_action_type_model';
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { useMemo } from 'react';
import { useQuery } from '@kbn/react-query';
import { ACTION_TYPE_SOURCES } from '@kbn/actions-types';
import type { ActionType } from '@kbn/actions-types';
import { fromConnectorSpecSchema } from '@kbn/connector-specs';
import type { HttpSetup, IUiSettingsClient } from '@kbn/core/public';
import type { ActionTypeModel, ActionTypeRegistryContract } from '../types';
import {
fetchConnectorSpec,
transformSpecToActionTypeModel,
} from '../utils/action_type_model_utils';

const CONNECTOR_SPEC_QUERY_KEY = 'connectorSpec';

export interface UseActionTypeModelResult {
/** The action type model, either from registry or derived from spec */
actionTypeModel: ActionTypeModel | null;
/** Whether the spec is currently being fetched */
isLoading: boolean;
/** Error if fetching the spec failed */
error: Error | null;
/** Whether the model was derived from a spec (vs from registry) */
isFromSpec: boolean;
/** Re-runs the connector spec query (no-op when the model is from the registry) */
refetch: () => void;
}

/**
* Hook to get an ActionTypeModel for a given ActionType.
*
* For stack connectors (registered in the actionTypeRegistry), returns the model synchronously.
* For spec-based connectors, fetches the spec from the API and transforms it into an ActionTypeModel.
*/
export function useActionTypeModel({
actionTypeRegistry,
actionType,
http,
uiSettings,
}: {
actionTypeRegistry: ActionTypeRegistryContract;
actionType: ActionType | null;
http: HttpSetup;
uiSettings?: IUiSettingsClient;
}): UseActionTypeModelResult {
const registeredModel = useMemo(() => {
if (actionType == null) {
return null;
}
if (actionTypeRegistry.has(actionType.id)) {
return actionTypeRegistry.get(actionType.id);
}
return null;
}, [actionType, actionTypeRegistry]);

const shouldFetchSpec = actionType != null && actionType.source === ACTION_TYPE_SOURCES.spec;

const {
data: specData,
isLoading,
error,
refetch,
} = useQuery<Awaited<ReturnType<typeof fetchConnectorSpec>>, Error>({
queryKey: [CONNECTOR_SPEC_QUERY_KEY, actionType?.id],
queryFn: async ({ signal }) => {
const spec = await fetchConnectorSpec(http, actionType!.id, signal);
// Validate eagerly — fail fast before caching. The schema is re-parsed
// lazily inside actionConnectorFields when the form component mounts.
if (!fromConnectorSpecSchema(spec.schema)) {
throw new Error(`Failed to parse connector spec schema for "${actionType!.id}"`);
}
return spec;
},
enabled: shouldFetchSpec,
staleTime: 5 * 60 * 1000,
refetchOnWindowFocus: false,
});

const specBasedModel = useMemo(() => {
if (!specData) {
return null;
}
return transformSpecToActionTypeModel(specData, uiSettings);
}, [specData, uiSettings]);

return {
actionTypeModel: shouldFetchSpec ? specBasedModel : registeredModel,
isLoading: shouldFetchSpec && isLoading,
error,
isFromSpec: shouldFetchSpec && specBasedModel != null,
refetch: () => {
void refetch();
},
};
}
Loading
Loading