diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index eafacb55a00ab..cdc57e4cc2884 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1229,8 +1229,6 @@ x-pack/solutions/security/test/plugin_functional/plugins/resolver_test @elastic/ x-pack/solutions/security/test/security_solution_api_integration @elastic/security-detection-engine x-pack/solutions/security/test/security_solution_api_integration/config/services/detections_response @elastic/security-detection-engine x-pack/solutions/security/test/security_solution_endpoint @elastic/security-defend-workflows -x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components @elastic/search-kibana @elastic/workchat-eng -x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server @elastic/search-kibana @elastic/workchat-eng x-pack/solutions/workplaceai/plugins/serverless_workplace_ai @elastic/search-kibana @elastic/workchat-eng x-pack/solutions/workplaceai/plugins/workplace_ai_app @elastic/search-kibana @elastic/workchat-eng x-pack/solutions/workplaceai/test @elastic/workchat-eng diff --git a/package.json b/package.json index c2fdac481660e..84986f157cf26 100644 --- a/package.json +++ b/package.json @@ -1169,8 +1169,6 @@ "@kbn/workflows-management-plugin": "link:src/platform/plugins/shared/workflows_management", "@kbn/workflows-ui": "link:src/platform/packages/shared/kbn-workflows-ui", "@kbn/workplace-ai-app": "link:x-pack/solutions/workplaceai/plugins/workplace_ai_app", - "@kbn/workplaceai-api-keys-components": "link:x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components", - "@kbn/workplaceai-api-keys-server": "link:x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server", "@kbn/xstate-utils": "link:src/platform/packages/shared/kbn-xstate-utils", "@kbn/zod": "link:src/platform/packages/shared/kbn-zod", "@kbn/zod-helpers": "link:src/platform/packages/shared/kbn-zod-helpers", diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 4afa6e475e8a8..870fd153785d1 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -7,7 +7,7 @@ pageLoadAssetSize: apm: 38573 apmSourcesAccess: 2278 automaticImport: 12162 - automaticImportVTwo: 4694 + automaticImportVTwo: 4662 banners: 4087 canvas: 15142 cases: 153204 @@ -89,7 +89,7 @@ pageLoadAssetSize: inputControlVis: 7660 inspectComponent: 4900 inspector: 17954 - interactiveSetup: 36654 + interactiveSetup: 36524 intercepts: 19011 kibanaOverview: 6339 kibanaReact: 22503 @@ -123,7 +123,7 @@ pageLoadAssetSize: observabilityLogsExplorer: 4918 observabilityOnboarding: 12872 observabilityShared: 75115 - onechat: 25223 + onechat: 25218 osquery: 47422 painlessLab: 6299 presentationPanel: 11418 @@ -205,5 +205,5 @@ pageLoadAssetSize: visualizations: 38375 watcher: 10485 workflowsExtensions: 2554 - workflowsManagement: 8545 - workplaceAIApp: 6454 + workflowsManagement: 8497 + workplaceAIApp: 6135 diff --git a/src/platform/packages/shared/deeplinks/agent_builder/constants.ts b/src/platform/packages/shared/deeplinks/agent_builder/constants.ts index 19a4a14374916..7106a7dcd1d82 100644 --- a/src/platform/packages/shared/deeplinks/agent_builder/constants.ts +++ b/src/platform/packages/shared/deeplinks/agent_builder/constants.ts @@ -8,4 +8,3 @@ */ export const AGENT_BUILDER_APP_ID = 'agent_builder'; -export const AGENT_BUILDER_AGENTS_CREATE = 'agents_create'; diff --git a/src/platform/packages/shared/deeplinks/agent_builder/index.ts b/src/platform/packages/shared/deeplinks/agent_builder/index.ts index 8c58978b949b8..39d2036de8590 100644 --- a/src/platform/packages/shared/deeplinks/agent_builder/index.ts +++ b/src/platform/packages/shared/deeplinks/agent_builder/index.ts @@ -7,5 +7,5 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { AGENT_BUILDER_APP_ID, AGENT_BUILDER_AGENTS_CREATE } from './constants'; +export { AGENT_BUILDER_APP_ID } from './constants'; export type { DeepLinkId } from './deep_links'; diff --git a/tsconfig.base.json b/tsconfig.base.json index de9ffbbeaefc2..3e5345631876f 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -2448,10 +2448,6 @@ "@kbn/workflows-ui/*": ["src/platform/packages/shared/kbn-workflows-ui/*"], "@kbn/workplace-ai-app": ["x-pack/solutions/workplaceai/plugins/workplace_ai_app"], "@kbn/workplace-ai-app/*": ["x-pack/solutions/workplaceai/plugins/workplace_ai_app/*"], - "@kbn/workplaceai-api-keys-components": ["x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components"], - "@kbn/workplaceai-api-keys-components/*": ["x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/*"], - "@kbn/workplaceai-api-keys-server": ["x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server"], - "@kbn/workplaceai-api-keys-server/*": ["x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/*"], "@kbn/workspaces": ["src/platform/packages/shared/kbn-workspaces"], "@kbn/workspaces/*": ["src/platform/packages/shared/kbn-workspaces/*"], "@kbn/xstate-utils": ["src/platform/packages/shared/kbn-xstate-utils"], diff --git a/x-pack/platform/plugins/shared/onechat/moon.yml b/x-pack/platform/plugins/shared/onechat/moon.yml index faae22443fca9..96257ef3f42bf 100644 --- a/x-pack/platform/plugins/shared/onechat/moon.yml +++ b/x-pack/platform/plugins/shared/onechat/moon.yml @@ -97,7 +97,6 @@ dependsOn: - '@kbn/shared-ux-utility' - '@kbn/usage-collection-plugin' - '@kbn/core-notifications-browser' - - '@kbn/deeplinks-agent-builder' tags: - plugin - prod diff --git a/x-pack/platform/plugins/shared/onechat/public/register.ts b/x-pack/platform/plugins/shared/onechat/public/register.ts index cd4d84ad301b6..72e15e40e1b88 100644 --- a/x-pack/platform/plugins/shared/onechat/public/register.ts +++ b/x-pack/platform/plugins/shared/onechat/public/register.ts @@ -9,7 +9,6 @@ import type { AppMountParameters } from '@kbn/core-application-browser'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; import type { CoreSetup } from '@kbn/core-lifecycle-browser'; import type { AnalyticsServiceSetup } from '@kbn/core/public'; -import { AGENT_BUILDER_AGENTS_CREATE } from '@kbn/deeplinks-agent-builder'; import { i18n } from '@kbn/i18n'; import type { ManagementSetup } from '@kbn/management-plugin/public'; import { eventTypes } from '../common/events'; @@ -54,13 +53,6 @@ export const registerApp = ({ path: '/agents', title: i18n.translate('xpack.onechat.agents.title', { defaultMessage: 'Agents' }), }, - { - id: AGENT_BUILDER_AGENTS_CREATE, - path: '/agents/new', - title: i18n.translate('xpack.onechat.agents.createTitle', { - defaultMessage: 'Create Agent', - }), - }, ], async mount({ element, history, onAppLeave }: AppMountParameters) { const { mountApp } = await import('./application'); diff --git a/x-pack/platform/plugins/shared/onechat/tsconfig.json b/x-pack/platform/plugins/shared/onechat/tsconfig.json index 05d5596182069..51bf03192dea5 100644 --- a/x-pack/platform/plugins/shared/onechat/tsconfig.json +++ b/x-pack/platform/plugins/shared/onechat/tsconfig.json @@ -93,6 +93,5 @@ "@kbn/shared-ux-utility", "@kbn/usage-collection-plugin", "@kbn/core-notifications-browser", - "@kbn/deeplinks-agent-builder", ] } diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/README.md b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/README.md deleted file mode 100644 index 81a32e0dfd2ec..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# WorkplaceAI API Key Components - -The Workplace AI API Keys components package is a shared components and utilities to simplify managing the API Keys experience for elasticsearch users across stack and serverless workplaceai solutions. \ No newline at end of file diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/index.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/index.ts deleted file mode 100644 index c57c51920f328..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './src/components/api_key_flyout_wrapper'; -export * from './src/components/api_key_form'; -export * from './src/hooks/use_workplaceai_api_key'; -export * from './src/constants'; diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/kibana.jsonc b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/kibana.jsonc deleted file mode 100644 index b3ecbaf65e850..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/kibana.jsonc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "shared-browser", - "id": "@kbn/workplaceai-api-keys-components", - "owner": [ - "@elastic/search-kibana", - "@elastic/workchat-eng" - ], - "group": "workplaceai", - "visibility": "private" -} \ No newline at end of file diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/moon.yml b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/moon.yml deleted file mode 100644 index 45ed46d867789..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/moon.yml +++ /dev/null @@ -1,38 +0,0 @@ -# This file is generated by the @kbn/moon package. Any manual edits will be erased! -# To extend this, write your extensions/overrides to 'moon.extend.yml' -# then regenerate this file with: 'node scripts/regenerate_moon_projects.js --update --filter @kbn/workplaceai-api-keys-components' - -$schema: https://moonrepo.dev/schemas/project.json -id: '@kbn/workplaceai-api-keys-components' -type: unknown -owners: - defaultOwner: '@elastic/search-kibana' -toolchain: - default: node -language: typescript -project: - name: '@kbn/workplaceai-api-keys-components' - description: Moon project for @kbn/workplaceai-api-keys-components - channel: '' - owner: '@elastic/search-kibana' - metadata: - sourceRoot: x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components -dependsOn: - - '@kbn/i18n' - - '@kbn/i18n-react' - - '@kbn/kibana-react-plugin' - - '@kbn/security-api-key-management' - - '@kbn/workplaceai-api-keys-server' - - '@kbn/react-query' -tags: - - shared-browser - - package - - prod - - group-workplaceai - - private -fileGroups: - src: - - src/**/* - - index.ts - - '!target/**/*' -tasks: {} diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/package.json b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/package.json deleted file mode 100644 index 9cccb021b3a6d..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@kbn/workplaceai-api-keys-components", - "private": true, - "version": "1.0.0", - "license": "Elastic License 2.0" -} \ No newline at end of file diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/components/api_key_flyout_wrapper.tsx b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/components/api_key_flyout_wrapper.tsx deleted file mode 100644 index e5c558e838cd0..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/components/api_key_flyout_wrapper.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import type { ApiKeyFlyoutProps } from '@kbn/security-api-key-management'; -import { ApiKeyFlyout } from '@kbn/security-api-key-management'; -import type { SecurityCreateApiKeyResponse } from '@elastic/elasticsearch/lib/api/types'; - -const API_KEY_NAME = 'Unrestricted API Key'; - -type ApiKeyFlyoutWrapperProps = Pick & { - onSuccess?: (createApiKeyResponse: SecurityCreateApiKeyResponse) => void; -}; - -export const ApiKeyFlyoutWrapper: React.FC = ({ - onCancel, - onSuccess, -}) => { - return ; -}; diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/components/api_key_form.tsx b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/components/api_key_form.tsx deleted file mode 100644 index 3e5630dd89185..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/components/api_key_form.tsx +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { - EuiBadge, - EuiButton, - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiCopy, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; -import { ApiKeyFlyoutWrapper } from './api_key_flyout_wrapper'; -import { useWorkplaceAIApiKey } from '../hooks/use_workplaceai_api_key'; -import { Status } from '../constants'; - -const API_KEY_MASK = '•'.repeat(60); - -interface ApiKeyFormProps { - hasTitle?: boolean; -} - -export const ApiKeyForm: React.FC = ({ hasTitle = true }) => { - const [showFlyout, setShowFlyout] = useState(false); - const { apiKey, status, updateApiKey, toggleApiKeyVisibility } = useWorkplaceAIApiKey(); - - if (apiKey) { - return ( - - - - {status === Status.showPreviewKey ? apiKey : API_KEY_MASK} - - - - - {(copy) => ( - - )} - - - - - - - ); - } - - return ( - - {hasTitle && ( - - -
- -
-
-
- )} - {status === Status.showUserPrivilegesError && ( - - - - - - )} - {status === Status.loading && ( - - - - - - )} - {(status === Status.showCreateButton || status === Status.uninitialized) && ( - - setShowFlyout(true)} - data-test-subj="createAPIKeyButton" - > - {hasTitle ? ( - - ) : ( - - )} - - {showFlyout && ( - setShowFlyout(false)} onSuccess={updateApiKey} /> - )} - - )} -
- ); -}; diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/constants.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/constants.ts deleted file mode 100644 index b58e2ece86455..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export enum Status { - uninitialized = 'uninitialized', - loading = 'loading', - showCreateButton = 'showCreateButton', - showHiddenKey = 'showHiddenKey', - showPreviewKey = 'showPreviewKey', - showUserPrivilegesError = 'showUserPrivilegesError', -} diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/hooks/use_create_api_key.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/hooks/use_create_api_key.ts deleted file mode 100644 index b1cc67ef6f28b..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/hooks/use_create_api_key.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useMutation } from '@kbn/react-query'; -import type { APIKeyCreationResponse } from '@kbn/workplaceai-api-keys-server/types'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { APIRoutes } from '../types'; - -export const useCreateApiKey = ({ - onSuccess, - onError, -}: { - onSuccess(key: APIKeyCreationResponse): void; - onError(err: XMLHttpRequest): void; -}) => { - const { http } = useKibana().services; - const { mutateAsync: createApiKey } = useMutation({ - mutationFn: async () => { - try { - if (!http?.post) { - throw new Error('HTTP service is unavailable'); - } - - return await http.post(APIRoutes.API_KEYS); - } catch (err) { - onError(err); - } - }, - onSuccess: (receivedApiKey) => { - if (receivedApiKey) { - onSuccess(receivedApiKey); - } - }, - }); - - return createApiKey; -}; diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/hooks/use_validate_api_key.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/hooks/use_validate_api_key.ts deleted file mode 100644 index 7a68ff5e5a31e..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/hooks/use_validate_api_key.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useMutation } from '@kbn/react-query'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { APIRoutes } from '../types'; - -export const useValidateApiKey = (): ((id: string) => Promise) => { - const { http } = useKibana().services; - const { mutateAsync: validateApiKey } = useMutation(async (id: string) => { - try { - if (!http?.post) { - throw new Error('HTTP service is unavailable'); - } - - const response = await http.post<{ isValid: boolean }>(APIRoutes.API_KEY_VALIDITY, { - body: JSON.stringify({ id }), - }); - - return response.isValid; - } catch (err) { - return false; - } - }); - - return validateApiKey; -}; diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/hooks/use_workplaceai_api_key.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/hooks/use_workplaceai_api_key.ts deleted file mode 100644 index 7bbe3a7f36c88..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/hooks/use_workplaceai_api_key.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useState, useEffect } from 'react'; -import { EventEmitter } from 'events'; -import { useCreateApiKey } from './use_create_api_key'; -import { useValidateApiKey } from './use_validate_api_key'; -import { Status } from '../constants'; - -const API_KEY_STORAGE_KEY = 'workplaceaiApiKey'; - -interface UseWorkplaceAIApiKeyParams { - apiKey: string | null; - toggleApiKeyVisibility: () => void; - updateApiKey: ({ id, encoded }: { id: string; encoded: string }) => void; - status: Status; -} - -interface ApiKeyState { - apiKey: string | null; - status: Status; -} - -interface ApiKeyEventEmitter extends EventEmitter { - on(event: 'change', listener: (state: ApiKeyState) => void): this; - emit(event: 'change', state: ApiKeyState): boolean; -} - -const apiKeyState: ApiKeyState = { - apiKey: null, - status: Status.uninitialized, -}; - -const apiKeyEmitter: ApiKeyEventEmitter = new EventEmitter(); - -const updateApiKeyState = (newState: Partial) => { - Object.assign(apiKeyState, newState); - apiKeyEmitter.emit('change', apiKeyState); -}; - -export const useWorkplaceAIApiKey = (): UseWorkplaceAIApiKeyParams => { - const [state, setState] = useState(apiKeyState); - - const validateApiKey = useValidateApiKey(); - const createApiKey = useCreateApiKey({ - onSuccess: (receivedApiKey) => { - if (receivedApiKey) { - sessionStorage.setItem( - API_KEY_STORAGE_KEY, - JSON.stringify({ id: receivedApiKey.id, encoded: receivedApiKey.encoded }) - ); - updateApiKeyState({ apiKey: receivedApiKey.encoded, status: Status.showHiddenKey }); - } - }, - onError: (err) => { - if (err.response?.status === 400) { - updateApiKeyState({ status: Status.showCreateButton }); - } else if (err.response?.status === 403) { - updateApiKeyState({ status: Status.showUserPrivilegesError }); - } else { - throw err; - } - }, - }); - - useEffect(() => { - const handleChange = (newState: ApiKeyState) => { - setState({ ...newState }); - }; - - apiKeyEmitter.on('change', handleChange); - - if ( - [Status.uninitialized, Status.showHiddenKey, Status.showPreviewKey].includes( - apiKeyState.status - ) - ) { - (async () => { - try { - const prevState = apiKeyState.status; - updateApiKeyState({ status: Status.loading }); - const storedKey = sessionStorage.getItem(API_KEY_STORAGE_KEY); - - if (storedKey) { - const { id, encoded } = JSON.parse(storedKey); - - if (await validateApiKey(id)) { - updateApiKeyState({ - apiKey: encoded, - status: prevState !== Status.uninitialized ? prevState : Status.showHiddenKey, - }); - } else { - sessionStorage.removeItem(API_KEY_STORAGE_KEY); - updateApiKeyState({ apiKey: null, status: Status.showCreateButton }); - await createApiKey(); - } - } else { - await createApiKey(); - } - } catch (e) { - updateApiKeyState({ apiKey: null, status: Status.showCreateButton }); - } - })(); - } - - return () => { - apiKeyEmitter.off('change', handleChange); - }; - }, [validateApiKey, createApiKey]); - - const updateApiKey = ({ id, encoded }: { id: string; encoded: string }) => { - sessionStorage.setItem(API_KEY_STORAGE_KEY, JSON.stringify({ id, encoded })); - updateApiKeyState({ apiKey: encoded, status: Status.showHiddenKey }); - }; - - const toggleApiKeyVisibility = () => { - const newStatus = - apiKeyState.status === Status.showHiddenKey ? Status.showPreviewKey : Status.showHiddenKey; - updateApiKeyState({ status: newStatus }); - }; - - return { - apiKey: state.apiKey, - status: state.status, - updateApiKey, - toggleApiKeyVisibility, - }; -}; diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/types.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/types.ts deleted file mode 100644 index c08a89aa319a6..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/src/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export enum APIRoutes { - API_KEYS = '/internal/workplaceai_api_keys', - API_KEY_VALIDITY = '/internal/workplaceai_api_keys/validity', -} diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/tsconfig.json b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/tsconfig.json deleted file mode 100644 index c471f3818fea2..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "@kbn/tsconfig-base/tsconfig.json", - "compilerOptions": { - "outDir": "target/types" - }, - "include": [ - "src/**/*", - "index.ts" - ], - "kbn_references": [ - "@kbn/i18n", - "@kbn/i18n-react", - "@kbn/kibana-react-plugin", - "@kbn/security-api-key-management", - "@kbn/workplaceai-api-keys-server", - "@kbn/react-query" - ], - "exclude": [ - "target/**/*" - ] -} diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/README.md b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/README.md deleted file mode 100644 index 76fca8e706fc1..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# WorkplaceAI API Keys - -The Workplace AI API Keys server package is a shared server APIs and utilities to simplify managing the API Keys experience for elasticsearch users across stack and serverless workplaceai solutions. \ No newline at end of file diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/index.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/index.ts deleted file mode 100644 index 48a7a83bd2dff..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './src/routes/routes'; -export * from './types'; diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/kibana.jsonc b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/kibana.jsonc deleted file mode 100644 index 9d350bb716d5f..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/kibana.jsonc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "shared-server", - "id": "@kbn/workplaceai-api-keys-server", - "owner": [ - "@elastic/search-kibana", - "@elastic/workchat-eng" - ], - "group": "workplaceai", - "visibility": "private" -} \ No newline at end of file diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/moon.yml b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/moon.yml deleted file mode 100644 index a25edb87e1967..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/moon.yml +++ /dev/null @@ -1,38 +0,0 @@ -# This file is generated by the @kbn/moon package. Any manual edits will be erased! -# To extend this, write your extensions/overrides to 'moon.extend.yml' -# then regenerate this file with: 'node scripts/regenerate_moon_projects.js --update --filter @kbn/workplaceai-api-keys-server' - -$schema: https://moonrepo.dev/schemas/project.json -id: '@kbn/workplaceai-api-keys-server' -type: unknown -owners: - defaultOwner: '@elastic/search-kibana' -toolchain: - default: node -language: typescript -project: - name: '@kbn/workplaceai-api-keys-server' - description: Moon project for @kbn/workplaceai-api-keys-server - channel: '' - owner: '@elastic/search-kibana' - metadata: - sourceRoot: x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server -dependsOn: - - '@kbn/core-elasticsearch-server' - - '@kbn/logging' - - '@kbn/core' - - '@kbn/config-schema' - - '@kbn/i18n' -tags: - - shared-server - - package - - prod - - group-workplaceai - - private -fileGroups: - src: - - src/**/* - - types.ts - - index.ts - - '!target/**/*' -tasks: {} diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/package.json b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/package.json deleted file mode 100644 index 97f8baaafc748..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@kbn/workplaceai-api-keys-server", - "private": true, - "version": "1.0.0", - "license": "Elastic License 2.0" -} \ No newline at end of file diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/lib/create_key.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/lib/create_key.ts deleted file mode 100644 index 96bc96bb9f4a7..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/lib/create_key.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { Logger } from '@kbn/logging'; -import type { APIKeyCreationResponse } from '../../types'; - -export async function createAPIKey( - name: string, - client: ElasticsearchClient, - logger: Logger -): Promise { - try { - const apiKey = await client.security.createApiKey({ - name, - role_descriptors: {}, - }); - - return apiKey; - } catch (e) { - logger.error(`Workplace AI API Keys: Error during creating API Key`); - logger.error(e); - throw e; - } -} diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/lib/get_key_by_id.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/lib/get_key_by_id.ts deleted file mode 100644 index 596245fb3024a..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/lib/get_key_by_id.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { Logger } from '@kbn/logging'; -import type { GetApiKeyResponse } from '../../types'; - -export async function getAPIKeyById( - id: string, - client: ElasticsearchClient, - logger: Logger -): Promise { - try { - const apiKey = await client.security.getApiKey({ - id, - }); - - return apiKey.api_keys?.[0]; - } catch (e) { - logger.error(`Workplace AI API Keys: Error on getting API Key`); - logger.error(e); - throw e; - } -} diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/lib/privileges.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/lib/privileges.ts deleted file mode 100644 index 585d12a0cac2a..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/lib/privileges.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { Logger } from '@kbn/logging'; - -export async function fetchUserStartPrivileges( - client: ElasticsearchClient, - logger: Logger -): Promise { - try { - // relying on manage cluster privilege to check if user can create API keys - // and can also have permissions for index monitoring - const securityCheck = await client.security.hasPrivileges({ - cluster: ['manage'], - index: [ - { - names: ['*'], - privileges: ['read', 'write'], - }, - ], - }); - - return securityCheck.has_all_requested ?? false; - } catch (e) { - logger.error(`Error checking user privileges for workplace AI API Keys`); - logger.error(e); - return false; - } -} - -export async function fetchClusterHasApiKeys( - client: ElasticsearchClient, - logger: Logger -): Promise { - try { - const clusterApiKeys = await client.security.queryApiKeys({ - query: { - term: { - invalidated: false, - }, - }, - }); - return clusterApiKeys.api_keys.length > 0; - } catch (e) { - logger.error(`Error checking cluster for existing valid API keys`); - logger.error(e); - return true; - } -} diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/routes/routes.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/routes/routes.ts deleted file mode 100644 index 1f2ecd72b5554..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/src/routes/routes.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { IRouter } from '@kbn/core/server'; -import type { Logger } from '@kbn/logging'; -import { i18n } from '@kbn/i18n'; - -import { schema } from '@kbn/config-schema'; -import { APIRoutes } from '../../types'; -import { getAPIKeyById } from '../lib/get_key_by_id'; -import { createAPIKey } from '../lib/create_key'; -import { fetchClusterHasApiKeys, fetchUserStartPrivileges } from '../lib/privileges'; - -const API_KEY_NAME = i18n.translate('xpack.workplaceai.apiKeyServer.unrestrictedApiKeyName', { - defaultMessage: 'Unrestricted API Key', -}); - -export function registerWorkplaceAIApiKeysRoutes(router: IRouter, logger: Logger) { - router.post( - { - path: APIRoutes.API_KEY_VALIDITY, - security: { - authz: { - enabled: false, - reason: 'This route delegates authorization to the scoped ES client', - }, - }, - validate: { - body: schema.object({ - id: schema.string(), - }), - }, - options: { - access: 'internal', - }, - }, - async (context, request, response) => { - try { - const core = await context.core; - const client = core.elasticsearch.client.asCurrentUser; - const apiKey = await getAPIKeyById(request.body.id, client, logger); - - if (!apiKey) { - return response.customError({ - body: { - message: i18n.translate('xpack.workplaceai.apiKeyServer.apiKeyNotFoundErrorMessage', { - defaultMessage: 'API key is not found.', - }), - }, - statusCode: 404, - }); - } - - return response.ok({ - body: { isValid: !apiKey.invalidated }, - headers: { 'content-type': 'application/json' }, - }); - } catch (e) { - logger.error(`Error fetching API Key`); - logger.error(e); - return response.customError({ - body: { message: e.message }, - statusCode: 500, - }); - } - } - ); - - router.post( - { - path: APIRoutes.API_KEYS, - security: { - authz: { - enabled: false, - reason: 'This route delegates authorization to the scoped ES client', - }, - }, - validate: {}, - options: { - access: 'internal', - }, - }, - async (context, _request, response) => { - try { - const core = await context.core; - const client = core.elasticsearch.client.asCurrentUser; - - const canCreateApiKeys = await fetchUserStartPrivileges(client, logger); - - if (!canCreateApiKeys) { - return response.customError({ - body: { - message: i18n.translate( - 'xpack.workplaceai.apiKeyServer.userNoPrivilegesErrorMessage', - { - defaultMessage: 'User does not have required privileges', - } - ), - }, - statusCode: 403, - }); - } - - const clusterHasApiKeys = await fetchClusterHasApiKeys(client, logger); - - if (clusterHasApiKeys) { - return response.customError({ - body: { - message: i18n.translate( - 'xpack.workplaceai.apiKeyServer.projectHasApiKeysErrorMessage', - { - defaultMessage: 'Project already has API keys', - } - ), - }, - statusCode: 400, - }); - } - - const apiKey = await createAPIKey(API_KEY_NAME, client, logger); - - return response.ok({ - body: apiKey, - headers: { 'content-type': 'application/json' }, - }); - } catch (e) { - logger.error(`Error creating API Key`); - logger.error(e); - return response.customError({ - body: { message: e.message }, - statusCode: 500, - }); - } - } - ); -} diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/tsconfig.json b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/tsconfig.json deleted file mode 100644 index e10c02eefb605..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "@kbn/tsconfig-base/tsconfig.json", - "compilerOptions": { - "outDir": "target/types" - }, - "include": [ - "src/**/*", - "types.ts", - "index.ts" - ], - "kbn_references": [ - "@kbn/core-elasticsearch-server", - "@kbn/logging", - "@kbn/core", - "@kbn/config-schema", - "@kbn/i18n" - ], - "exclude": [ - "target/**/*" - ] -} diff --git a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/types.ts b/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/types.ts deleted file mode 100644 index d2f44b0ac875c..0000000000000 --- a/x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export enum APIRoutes { - API_KEYS = '/internal/workplaceai_api_keys', - API_KEY_VALIDITY = '/internal/workplaceai_api_keys/validity', -} - -export interface APIKey { - id: string; - name: string; - expiration?: number; - invalidated?: boolean; -} - -export interface APIKeyCreationResponse extends Pick { - api_key: string; - encoded: string; -} - -export type GetApiKeyResponse = APIKey; diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/common/index.ts b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/common/index.ts index f44046df3e727..fecddaf92a88f 100644 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/common/index.ts +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/common/index.ts @@ -8,6 +8,9 @@ export { AGENT_BUILDER_API_PATH, GET_AGENTS_ROUTE, - AGENT_BUILDER_AGENTS, MCP_SERVER_PATH, + AGENT_BUILDER_AGENT_NEW_PATH, + AGENT_BUILDER_CONVERSATIONS_NEW_PATH, + AGENT_BUILDER_AGENTS, + STACK_CONNECTORS_MANAGEMENT_ID, } from './routes'; diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/common/routes.ts b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/common/routes.ts index 788c0570765f0..21cbddfe70886 100644 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/common/routes.ts +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/common/routes.ts @@ -9,3 +9,8 @@ export const AGENT_BUILDER_API_PATH = '/api/agent_builder'; export const GET_AGENTS_ROUTE = `${AGENT_BUILDER_API_PATH}/agents`; export const AGENT_BUILDER_AGENTS = 'agents'; export const MCP_SERVER_PATH = `${AGENT_BUILDER_API_PATH}/mcp`; +export const AGENT_BUILDER_AGENT_NEW_PATH = '/agents/new'; +export const AGENT_BUILDER_CONVERSATIONS_NEW_PATH = '/conversations/new'; + +// Stack Management deeplinks +export const STACK_CONNECTORS_MANAGEMENT_ID = 'management:triggersActionsConnectors'; diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/kibana.jsonc b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/kibana.jsonc index 8cadf0ee53888..4407e3785ebf5 100644 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/kibana.jsonc +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/kibana.jsonc @@ -13,8 +13,8 @@ "server": true, "browser": true, "configPath": ["xpack", "workplaceAIApp"], - "requiredPlugins": ["inference", "actions", "features", "dataSourcesRegistry", "spaces"], + "requiredPlugins": ["inference", "actions", "features", "dataSourcesRegistry", "spaces", "triggersActionsUi"], "optionalPlugins": ["cloud", "share"], - "requiredBundles": ["kibanaReact"] + "requiredBundles": ["kibanaReact", "stackConnectors"] } } diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/moon.yml b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/moon.yml index a1a2bf0d54a35..ff7ff8dbdd850 100644 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/moon.yml +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/moon.yml @@ -34,8 +34,6 @@ dependsOn: - '@kbn/logging' - '@kbn/data-sources-registry-plugin' - '@kbn/react-query' - - '@kbn/workplaceai-api-keys-components' - - '@kbn/workplaceai-api-keys-server' - '@kbn/deeplinks-agent-builder' - '@kbn/deeplinks-data-connectors' - '@kbn/deeplinks-analytics' @@ -45,6 +43,10 @@ dependsOn: - '@kbn/cloud-plugin' - '@kbn/share-plugin' - '@kbn/spaces-plugin' + - '@kbn/triggers-actions-ui-plugin' + - '@kbn/elastic-assistant' + - '@kbn/ai-assistant-connector-selector-action' + - '@kbn/core-application-browser' tags: - plugin - prod diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/agent_selector/agent_selector.tsx b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/agent_selector/agent_selector.tsx new file mode 100644 index 0000000000000..73d79f1176f20 --- /dev/null +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/agent_selector/agent_selector.tsx @@ -0,0 +1,169 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useMemo, useCallback } from 'react'; +import type { EuiSelectableOption, UseEuiTheme } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiPopover, + EuiPopoverTitle, + EuiSelectable, + EuiText, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiPopoverFooter, + EuiButton, + EuiLoadingSpinner, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { AGENT_BUILDER_APP_ID } from '@kbn/deeplinks-agent-builder'; +import { AGENT_BUILDER_AGENTS, AGENT_BUILDER_AGENT_NEW_PATH } from '../../../../common'; +import { useAgents } from '../../hooks/use_agents'; +import { useNavigateToApp } from '../../hooks/use_navigate_to_app'; + +const AGENT_SELECT_ID = 'workplaceAIAgentSelect'; + +const panelStyles = ({ euiTheme }: UseEuiTheme) => ({ + inlineSize: `calc(${euiTheme.size.xxl} * 7)`, +}); + +const agentsTitleStyles = ({ euiTheme }: UseEuiTheme) => ({ + fontWeight: euiTheme.font.weight.bold, +}); + +const labels = { + selectAgent: i18n.translate('xpack.workplaceai.agentSelector.selectAgent', { + defaultMessage: 'Select agent', + }), + agents: i18n.translate('xpack.workplaceai.agentSelector.agents', { + defaultMessage: 'Agents', + }), + manageAgents: i18n.translate('xpack.workplaceai.agentSelector.manageAgents', { + defaultMessage: 'Manage agents', + }), + createAgent: i18n.translate('xpack.workplaceai.agentSelector.createAgent', { + defaultMessage: 'Create an agent', + }), + noAgentsAvailable: i18n.translate('xpack.workplaceai.agentSelector.noAgentsAvailable', { + defaultMessage: 'No agents available', + }), +}; + +interface AgentSelectorProps { + selectedAgentId?: string; + onAgentChange: (agentId: string) => void; +} + +export const AgentSelector: React.FC = ({ selectedAgentId, onAgentChange }) => { + const { data: agents = [], isLoading } = useAgents(); + const navigateToApp = useNavigateToApp(); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const selectedAgent = useMemo( + () => agents.find((agent) => agent.id === selectedAgentId), + [agents, selectedAgentId] + ); + + const options: EuiSelectableOption[] = useMemo( + () => + agents.map((agent) => ({ + key: agent.id, + label: agent.name, + checked: agent.id === selectedAgentId ? 'on' : undefined, + })), + [agents, selectedAgentId] + ); + + const handleAgentChange = useCallback( + (value: EuiSelectableOption[]) => { + const newAgentId = value.find((v) => v.checked === 'on')?.key; + if (newAgentId) { + onAgentChange(newAgentId); + setIsPopoverOpen(false); + } + }, + [onAgentChange] + ); + + const handleManageAgentsClick = useCallback(() => { + navigateToApp(`${AGENT_BUILDER_APP_ID}:${AGENT_BUILDER_AGENTS}`); + setIsPopoverOpen(false); + }, [navigateToApp]); + + const handleCreateAgentClick = useCallback(() => { + navigateToApp(AGENT_BUILDER_APP_ID, { path: AGENT_BUILDER_AGENT_NEW_PATH }); + setIsPopoverOpen(false); + }, [navigateToApp]); + + const agentSelectorButton = ( + setIsPopoverOpen(!isPopoverOpen)} + aria-haspopup="menu" + aria-labelledby={AGENT_SELECT_ID} + data-test-subj="workplaceAIAgentSelectorButton" + disabled={isLoading || agents.length === 0} + > + {isLoading ? : selectedAgent?.name || labels.noAgentsAvailable} + + ); + + return ( + setIsPopoverOpen(false)} + > + + {(list) => ( + <> + + + + + {labels.agents} + + + + + {labels.manageAgents} + + + + + {list} + + + {labels.createAgent} + + + + )} + + + ); +}; diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/connector_selector/connector_selector.tsx b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/connector_selector/connector_selector.tsx new file mode 100644 index 0000000000000..f0f028ebbc8fb --- /dev/null +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/connector_selector/connector_selector.tsx @@ -0,0 +1,139 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useMemo, useEffect } from 'react'; +import { EuiPopover, EuiButtonEmpty, EuiLoadingSpinner, type UseEuiTheme } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { + ConnectorSelectable, + type ConnectorSelectableComponentProps, +} from '@kbn/ai-assistant-connector-selector-action'; +import { useLoadConnectors } from '@kbn/elastic-assistant'; +import { STACK_CONNECTORS_MANAGEMENT_ID } from '../../../../common'; +import { useKibana } from '../../hooks/use_kibana'; +import { useNavigateToApp } from '../../hooks/use_navigate_to_app'; + +const labels = { + selectConnector: i18n.translate('xpack.workplaceai.connectorSelector.selectConnector', { + defaultMessage: 'Select connector', + }), + noConnector: i18n.translate('xpack.workplaceai.connectorSelector.noConnector', { + defaultMessage: 'No connector', + }), +}; + +const panelStyles = ({ euiTheme }: UseEuiTheme) => ({ + inlineSize: `calc(${euiTheme.size.xxl} * 8)`, +}); + +interface ConnectorSelectorProps { + selectedConnectorId?: string; + onSelectConnector: (connectorId: string) => void; + defaultConnectorId?: string; +} + +export const ConnectorSelector: React.FC = ({ + selectedConnectorId, + onSelectConnector, + defaultConnectorId, +}) => { + const navigateToApp = useNavigateToApp(); + const { + services: { http, uiSettings }, + } = useKibana(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const { data: dataConnectors, isLoading } = useLoadConnectors({ + http, + settings: { + client: uiSettings, + globalClient: uiSettings, + }, + inferenceEnabled: true, + }); + + const connectors = useMemo(() => dataConnectors ?? [], [dataConnectors]); + + const togglePopover = () => setIsPopoverOpen(!isPopoverOpen); + const closePopover = () => setIsPopoverOpen(false); + + const { preConfiguredConnectors, customConnectors } = useMemo(() => { + const preConfigured: ConnectorSelectableComponentProps['preConfiguredConnectors'] = []; + const custom: ConnectorSelectableComponentProps['customConnectors'] = []; + + connectors.forEach((connector) => { + const option = { + value: connector.id, + label: connector.name, + }; + + if (connector.isPreconfigured) { + preConfigured.push(option); + } else { + custom.push(option); + } + }); + + return { preConfiguredConnectors: preConfigured, customConnectors: custom }; + }, [connectors]); + + // Auto-select first connector if none selected + useEffect(() => { + if (!isLoading && connectors.length > 0 && !selectedConnectorId) { + const firstConnector = defaultConnectorId || connectors[0].id; + if (firstConnector) { + onSelectConnector(firstConnector); + } + } + }, [isLoading, connectors, selectedConnectorId, defaultConnectorId, onSelectConnector]); + + const selectedConnector = connectors.find((c) => c.id === selectedConnectorId); + const selectedConnectorName = selectedConnector?.name || selectedConnectorId; + const buttonLabel = selectedConnectorName || labels.noConnector; + + const connectorSelectorButton = ( + + {isLoading ? : buttonLabel} + + ); + + return ( + + { + onSelectConnector(connectorId); + closePopover(); + }} + customConnectors={customConnectors} + preConfiguredConnectors={preConfiguredConnectors} + defaultConnectorId={defaultConnectorId} + data-test-subj="workplaceAIConnectorSelector" + onAddConnectorClick={() => { + navigateToApp(STACK_CONNECTORS_MANAGEMENT_ID); + closePopover(); + }} + /> + + ); +}; diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/elasticsearch_url_field.tsx b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/elasticsearch_url_field.tsx deleted file mode 100644 index e84a826cf6b9f..0000000000000 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/elasticsearch_url_field.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiButtonIcon, EuiCopy, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -interface ElasticsearchUrlFieldProps { - value: string; - dataTestSubj?: string; - copyValueDataTestSubj?: string; -} - -export const ElasticsearchUrlField: React.FC = ({ - value, - dataTestSubj, - copyValueDataTestSubj, -}) => { - return ( - ({ - color: euiTheme.colors.textParagraph, - backgroundColor: euiTheme.colors.backgroundBaseSubdued, - borderRadius: euiTheme.border.radius.small, - })} - alignItems="center" - gutterSize="xs" - responsive={false} - > - - ({ - textOverflow: 'ellipsis', - overflow: 'hidden', - whiteSpace: 'nowrap', - fontSize: euiTheme.size.m, - padding: `${euiTheme.size.s} ${euiTheme.size.m}`, - })} - > - {value} - - - - - {(copy) => ( - - )} - - - - ); -}; diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/explore_agent_prompt.tsx b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/explore_agent_prompt.tsx new file mode 100644 index 0000000000000..76379aa3d5735 --- /dev/null +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/explore_agent_prompt.tsx @@ -0,0 +1,200 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useEffect } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiTextArea, + EuiButtonIcon, + useEuiTheme, + euiShadow, + euiShadowHover, + type UseEuiTheme, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { AGENT_BUILDER_APP_ID } from '@kbn/deeplinks-agent-builder'; +import { AGENT_BUILDER_CONVERSATIONS_NEW_PATH } from '../../../common'; +import { useKibana } from '../hooks/use_kibana'; +import { useAgents } from '../hooks/use_agents'; +import { AgentSelector } from './agent_selector/agent_selector'; +import { ConnectorSelector } from './connector_selector/connector_selector'; + +const INPUT_MIN_HEIGHT = '150px'; + +const titleStyles = { fontWeight: 400 }; + +const titleContainerStyles = { width: '100%' }; + +const fullWidthStyles = { width: '100%' }; + +const getInputContainerStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + return css` + width: 100%; + min-height: ${INPUT_MIN_HEIGHT}; + padding: ${euiTheme.size.base}; + flex-grow: 0; + transition: box-shadow 250ms; + background-color: ${euiTheme.colors.backgroundBasePlain}; + border: none; + + ${euiShadow(euiThemeContext, 's')} + &:hover { + ${euiShadowHover(euiThemeContext, 's')} + } + &:focus-within { + ${euiShadow(euiThemeContext, 'xl')} + :hover { + ${euiShadowHover(euiThemeContext, 'xl')} + } + } + `; +}; + +const textAreaStyles = css` + && { + border: none; + box-shadow: none; + outline: none; + padding: 0; + resize: none; + } + &&:focus { + border: none; + box-shadow: none; + outline: none; + background-image: none; + } +`; + +export const ExploreAgentPrompt: React.FC = () => { + const { + services: { application }, + } = useKibana(); + const euiThemeContext = useEuiTheme(); + const { data: agents = [] } = useAgents(); + const [chatInput, setChatInput] = useState(''); + const [selectedAgentId, setSelectedAgentId] = useState(); + const [selectedConnectorId, setSelectedConnectorId] = useState(); + + // Set default agent when agents are loaded + useEffect(() => { + if (agents.length > 0 && !selectedAgentId) { + setSelectedAgentId(agents[0].id); + } + }, [agents, selectedAgentId]); + + const inputContainerStyles = getInputContainerStyles(euiThemeContext); + + const handleSubmit = () => { + if (chatInput.trim() === '') { + return; + } + + // Navigate to Agent Builder with the message, agent ID, and connector ID in location state + application.navigateToApp(AGENT_BUILDER_APP_ID, { + path: AGENT_BUILDER_CONVERSATIONS_NEW_PATH, + state: { + initialMessage: chatInput.trim(), + agentId: selectedAgentId, + connectorId: selectedConnectorId, + }, + }); + }; + + return ( + + + +

+ {i18n.translate('xpack.workplaceai.exploreAgentPrompt.title', { + defaultMessage: 'How can I help you?', + })} +

+
+
+ + + + + setChatInput(e.target.value)} + rows={3} + fullWidth + css={textAreaStyles} + data-test-subj="workplaceAIExploreAgentInput" + /> + + + + + + + + + + + + + + + + + + + + + +
+ ); +}; diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/explore_default_agent.tsx b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/explore_default_agent.tsx deleted file mode 100644 index 2dc4d8f98bf63..0000000000000 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/explore_default_agent.tsx +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { - EuiPanel, - EuiTitle, - EuiSpacer, - EuiFlexGroup, - EuiFlexItem, - EuiButton, - EuiTextArea, - EuiButtonEmpty, - EuiButtonIcon, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; -import { AGENT_BUILDER_APP_ID } from '@kbn/deeplinks-agent-builder'; -import { useKibana } from '../hooks/use_kibana'; - -export const ExploreDefaultAgent: React.FC = () => { - const { - services: { application }, - } = useKibana(); - const [chatInput, setChatInput] = useState(''); - - const handleSubmit = () => { - if (chatInput.trim() === '') { - return; - } - - // Navigate to Agent Builder with the message in location state - application.navigateToApp(AGENT_BUILDER_APP_ID, { - path: '/conversations/new', - state: { - initialMessage: chatInput.trim(), - }, - }); - }; - - return ( - - -

- -

-
- - - - - {}}> - - - - - - - - setChatInput(e.target.value)} - rows={3} - resize="none" - fullWidth - style={{ border: 'none', boxShadow: 'none', outline: 'none', padding: 0 }} - /> - - - - - - - - {}} - aria-label={i18n.translate( - 'xpack.workplaceai.gettingStarted.exploreDefaultAgent.shareLinkAriaLabel', - { - defaultMessage: 'Share link', - } - )} - /> - - - {}} - > - - - - - {}} - > - - - - - - - - - - -
- ); -}; diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/explore_workplace_ai.tsx b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/explore_workplace_ai.tsx index 164925c8e329b..e039c7ae0ecb1 100644 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/explore_workplace_ai.tsx +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/explore_workplace_ai.tsx @@ -8,8 +8,9 @@ import React, { useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiCard, EuiButton, EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { AGENT_BUILDER_APP_ID, AGENT_BUILDER_AGENTS_CREATE } from '@kbn/deeplinks-agent-builder'; +import { AGENT_BUILDER_APP_ID } from '@kbn/deeplinks-agent-builder'; import { DATA_CONNECTORS_APP_ID } from '@kbn/deeplinks-data-connectors'; +import { AGENT_BUILDER_AGENT_NEW_PATH } from '../../../common'; import { useNavigateToApp } from '../hooks/use_navigate_to_app'; import searchWindowSVG from '../../assets/search_window_illustration.svg'; import searchAnalyticsSVG from '../../assets/search_analytics.svg'; @@ -23,7 +24,7 @@ export const ExploreWorkplaceAI: React.FC = () => { }, [navigateToApp]); const onCreateAgent = useCallback(() => { - navigateToApp(`${AGENT_BUILDER_APP_ID}:${AGENT_BUILDER_AGENTS_CREATE}`); + navigateToApp(AGENT_BUILDER_APP_ID, { path: AGENT_BUILDER_AGENT_NEW_PATH }); }, [navigateToApp]); const onChatNow = useCallback(() => { diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/home_view.tsx b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/home_view.tsx index ebb39d59dc31f..23daf6f461044 100644 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/home_view.tsx +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/home_view.tsx @@ -7,34 +7,36 @@ import React from 'react'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; -import { EuiSpacer } from '@elastic/eui'; +import type { UseEuiTheme } from '@elastic/eui'; import { WorkplaceAIHomeHeader } from './workplace_ai_home_header'; -import { ExploreDefaultAgent } from './explore_default_agent'; +import { ExploreAgentPrompt } from './explore_agent_prompt'; import { ExploreWorkplaceAI } from './explore_workplace_ai'; import { SnapshotsSection } from './snapshots_section'; import { WorkplaceAIHomeFooter } from './workplace_ai_home_footer'; +const sectionGapStyles = ({ euiTheme }: UseEuiTheme) => ({ + height: euiTheme.size.xxxxl, +}); + export const WorkplaceAIHomeView: React.FC<{}> = () => { return ( - + - +
- +
- +
- - ); diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/snapshots_section.tsx b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/snapshots_section.tsx index 8b5fb8bd63893..b0ec6a1d9b847 100644 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/snapshots_section.tsx +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/components/snapshots_section.tsx @@ -17,23 +17,76 @@ import { EuiIcon, EuiLink, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { AGENT_BUILDER_APP_ID } from '@kbn/deeplinks-agent-builder'; +import { DATA_CONNECTORS_APP_ID } from '@kbn/deeplinks-data-connectors'; +import type { AgentConfiguration, ToolSelection } from '@kbn/onechat-common'; import { AGENT_BUILDER_AGENTS } from '../../../common'; import { useAgents } from '../hooks/use_agents'; import { useKibana } from '../hooks/use_kibana'; +import { useNavigateToApp } from '../hooks/use_navigate_to_app'; import salesForceSVG from '../../assets/salesforce.svg'; import googleDriveSVG from '../../assets/google_drive.svg'; import confluenceSVG from '../../assets/confluence.svg'; +const MAX_DISPLAY_ITEMS = 4; +const CARD_MIN_HEIGHT = '320px'; + +const cardContainerStyles = css` + height: 100%; +`; + +const cardStyles = css` + height: 100%; + min-height: ${CARD_MIN_HEIGHT}; +`; + +// Hardcoded sources data +const allSources = [ + { + name: 'Salesforce', + status: 'Syncing', + icon: salesForceSVG, + }, + { + name: 'Google Drive', + status: 'Sync error', + icon: googleDriveSVG, + }, + { + name: 'Confluence', + status: 'Up to date', + icon: confluenceSVG, + }, + { + name: 'Slack', + status: 'Syncing', + icon: salesForceSVG, + }, + { + name: 'Jira', + status: 'Up to date', + icon: confluenceSVG, + }, + { + name: 'SharePoint', + status: 'Up to date', + icon: googleDriveSVG, + }, +]; + export const SnapshotsSection: React.FC = () => { const { data: agents = [], isLoading: isLoadingAgents } = useAgents(); const { services: { application, chrome }, } = useKibana(); + const navigateToApp = useNavigateToApp(); const agentCount = agents.length; + const sourceCount = allSources.length; + const syncingCount = allSources.filter((s) => s.status === 'Syncing').length; const getAgentEditUrl = useCallback( (agentId: string) => { @@ -51,14 +104,14 @@ export const SnapshotsSection: React.FC = () => {

- - - + + +

{ { }, }, { - field: 'type', - name: i18n.translate('xpack.workplaceai.gettingStarted.snapshots.typeColumn', { - defaultMessage: 'Type', + field: 'configuration', + name: i18n.translate('xpack.workplaceai.gettingStarted.snapshots.toolsColumn', { + defaultMessage: 'Tools', }), - render: (type: string) => type || '-', + render: (configuration?: AgentConfiguration) => { + const toolCount = + configuration?.tools?.reduce( + (count: number, selection: ToolSelection) => + count + (selection.tool_ids?.length ?? 0), + 0 + ) ?? 0; + return toolCount; + }, }, ]} /> + {agentCount > MAX_DISPLAY_ITEMS && ( + <> + + + + + navigateToApp(`${AGENT_BUILDER_APP_ID}:${AGENT_BUILDER_AGENTS}`) + } + > + + + + + + )} - - + +

{

{ defaultMessage: 'Source name', } ), - render: (name: string, item: any) => ( + render: (name: string, item: { icon: string }) => ( @@ -212,13 +259,7 @@ export const SnapshotsSection: React.FC = () => { }), render: (status: string) => ( - {status === - i18n.translate( - 'xpack.workplaceai.gettingStarted.snapshots.syncErrorStatus', - { - defaultMessage: 'Sync error', - } - ) && ( + {status === 'Sync error' && ( @@ -231,16 +272,31 @@ export const SnapshotsSection: React.FC = () => { }, ]} /> + {sourceCount > MAX_DISPLAY_ITEMS && ( + <> + + + + navigateToApp(DATA_CONNECTORS_APP_ID)}> + + + + + + )} - - + +

@@ -254,6 +310,12 @@ export const SnapshotsSection: React.FC = () => { { const user = useCurrentUser(); - const elasticsearchUrl = useElasticsearchUrl(); - const { mcpServerUrl } = useMcpServerUrl(); return ( @@ -56,49 +48,6 @@ export const WorkplaceAIHomeHeader: React.FC = () => { - - - - - - - - - {(copy) => ( - - - - )} - - { - const mockFetchElasticsearchConfig = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('returns placeholder URL when cloud plugin is unavailable', () => { - mockUseKibana.mockReturnValue({ - services: { - plugins: { - cloud: undefined, - }, - }, - }); - - const { result } = renderHook(() => useElasticsearchUrl()); - - expect(result.current).toBe('https://your_deployment_url'); - }); - - it('returns placeholder URL initially and updates with cloud URL', async () => { - mockFetchElasticsearchConfig.mockResolvedValue({ - elasticsearchUrl: 'https://cloud-es-url.elastic.co', - }); - - mockUseKibana.mockReturnValue({ - services: { - plugins: { - cloud: { - fetchElasticsearchConfig: mockFetchElasticsearchConfig, - }, - }, - }, - }); - - const { result } = renderHook(() => useElasticsearchUrl()); - - // Initially returns placeholder - expect(result.current).toBe('https://your_deployment_url'); - - // Wait for async update - await waitFor(() => { - expect(result.current).toBe('https://cloud-es-url.elastic.co'); - }); - - expect(mockFetchElasticsearchConfig).toHaveBeenCalledTimes(1); - }); -}); diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_elasticsearch_url.ts b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_elasticsearch_url.ts deleted file mode 100644 index 72ab4c5c3e96f..0000000000000 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_elasticsearch_url.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useEffect, useState } from 'react'; -import { useKibana } from './use_kibana'; - -const ELASTICSEARCH_URL_PLACEHOLDER = 'https://your_deployment_url'; - -export const useElasticsearchUrl = (): string => { - const { - services: { - plugins: { cloud }, - }, - } = useKibana(); - - const [elasticsearchUrl, setElasticsearchUrl] = useState(ELASTICSEARCH_URL_PLACEHOLDER); - - useEffect(() => { - cloud?.fetchElasticsearchConfig().then((config) => { - setElasticsearchUrl(config?.elasticsearchUrl || ELASTICSEARCH_URL_PLACEHOLDER); - }); - }, [cloud]); - - return elasticsearchUrl; -}; diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_mcp_server_url.test.ts b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_mcp_server_url.test.ts deleted file mode 100644 index 5003eae804341..0000000000000 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_mcp_server_url.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { renderHook } from '@testing-library/react'; -import { useMcpServerUrl } from './use_mcp_server_url'; -import { useKibana } from './use_kibana'; -import { useSpaceId } from './use_space_id'; - -jest.mock('./use_kibana'); -jest.mock('./use_space_id'); - -const mockUseKibana = useKibana as jest.Mock; -const mockUseSpaceId = useSpaceId as jest.Mock; - -describe('useMcpServerUrl', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockUseSpaceId.mockReturnValue('default'); - }); - - it('returns MCP URL using publicBaseUrl when available', () => { - mockUseKibana.mockReturnValue({ - services: { - http: { - basePath: { - publicBaseUrl: 'https://kibana.example.com', - serverBasePath: '', - get: jest.fn(() => '/base'), - }, - }, - plugins: { - cloud: undefined, - spaces: undefined, - }, - }, - }); - - const { result } = renderHook(() => useMcpServerUrl()); - - expect(result.current.mcpServerUrl).toBe('https://kibana.example.com/api/agent_builder/mcp'); - }); - - it('returns MCP URL using cloud.kibanaUrl when publicBaseUrl is unavailable', () => { - mockUseKibana.mockReturnValue({ - services: { - http: { - basePath: { - publicBaseUrl: undefined, - serverBasePath: '', - get: jest.fn(() => '/base'), - }, - }, - plugins: { - cloud: { - kibanaUrl: 'https://cloud.elastic.co', - }, - spaces: undefined, - }, - }, - }); - - const { result } = renderHook(() => useMcpServerUrl()); - - expect(result.current.mcpServerUrl).toBe('https://cloud.elastic.co/api/agent_builder/mcp'); - }); - - it('returns MCP URL using window.location as fallback', () => { - const originalLocation = window.location; - delete (window as any).location; - window.location = { origin: 'https://localhost:5601' } as any; - - mockUseKibana.mockReturnValue({ - services: { - http: { - basePath: { - publicBaseUrl: undefined, - serverBasePath: '', - get: jest.fn(() => '/s/default'), - }, - }, - plugins: { - cloud: undefined, - spaces: undefined, - }, - }, - }); - - const { result } = renderHook(() => useMcpServerUrl()); - - expect(result.current.mcpServerUrl).toBe( - 'https://localhost:5601/s/default/api/agent_builder/mcp' - ); - - // @ts-expect-error upgrade typescript v5.9.3 - window.location = originalLocation; - }); - - it('includes basePath in fallback URL', () => { - const originalLocation = window.location; - delete (window as any).location; - window.location = { origin: 'https://example.com' } as any; - - mockUseKibana.mockReturnValue({ - services: { - http: { - basePath: { - publicBaseUrl: undefined, - serverBasePath: '', - get: jest.fn(() => '/custom-base-path'), - }, - }, - plugins: { - cloud: undefined, - spaces: undefined, - }, - }, - }); - - const { result } = renderHook(() => useMcpServerUrl()); - - expect(result.current.mcpServerUrl).toBe( - 'https://example.com/custom-base-path/api/agent_builder/mcp' - ); - - // @ts-expect-error upgrade typescript v5.9.3 - window.location = originalLocation; - }); - - it('adds space ID to URL when not already present', () => { - mockUseSpaceId.mockReturnValue('my-space'); - mockUseKibana.mockReturnValue({ - services: { - http: { - basePath: { - publicBaseUrl: 'https://kibana.example.com', - serverBasePath: '', - get: jest.fn(() => '/base'), - }, - }, - plugins: { - cloud: undefined, - spaces: {}, - }, - }, - }); - - const { result } = renderHook(() => useMcpServerUrl()); - - expect(result.current.mcpServerUrl).toBe( - 'https://kibana.example.com/s/my-space/api/agent_builder/mcp' - ); - }); - - it('does not add space ID when already present in URL', () => { - mockUseSpaceId.mockReturnValue('my-space'); - mockUseKibana.mockReturnValue({ - services: { - http: { - basePath: { - publicBaseUrl: 'https://kibana.example.com/s/existing-space', - serverBasePath: '', - get: jest.fn(() => '/s/existing-space'), - }, - }, - plugins: { - cloud: undefined, - spaces: {}, - }, - }, - }); - - const { result } = renderHook(() => useMcpServerUrl()); - - expect(result.current.mcpServerUrl).toBe( - 'https://kibana.example.com/s/existing-space/api/agent_builder/mcp' - ); - }); -}); diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_mcp_server_url.ts b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_mcp_server_url.ts deleted file mode 100644 index d79f8e74ce5be..0000000000000 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_mcp_server_url.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useMemo } from 'react'; -import type { HttpSetup } from '@kbn/core/public'; -import { getSpaceIdFromPath, addSpaceIdToPath } from '@kbn/spaces-plugin/common'; -import { MCP_SERVER_PATH } from '../../../common'; -import { useKibana } from './use_kibana'; -import { useSpaceId } from './use_space_id'; - -export const useMcpServerUrl = () => { - const { - services: { http, plugins }, - } = useKibana(); - const spaceId = useSpaceId(plugins.spaces); - - const mcpServerUrl = useMemo(() => { - // Get the base URL, preferring publicBaseUrl, then cloud URL, then window location - const baseUrl = - http.basePath.publicBaseUrl ?? plugins.cloud?.kibanaUrl ?? getFallbackKibanaUrl(http); - - const pathname = new URL(baseUrl).pathname; - const serverBasePath = http.basePath.serverBasePath; - const { pathHasExplicitSpaceIdentifier } = getSpaceIdFromPath(pathname, serverBasePath); - - // Add space ID to the base URL if it doesn't already have one - const kibanaUrl = !pathHasExplicitSpaceIdentifier - ? addSpaceIdToPath(baseUrl, spaceId) - : baseUrl; - - return `${kibanaUrl}${MCP_SERVER_PATH}`; - }, [http, plugins.cloud, spaceId]); - - return { mcpServerUrl }; -}; - -export function getFallbackKibanaUrl(http: HttpSetup) { - return `${window.location.origin}${http.basePath.get()}`; -} diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_navigate_to_app.ts b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_navigate_to_app.ts index 1e07036f07448..647e3f00022e8 100644 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_navigate_to_app.ts +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/application/hooks/use_navigate_to_app.ts @@ -6,6 +6,7 @@ */ import { useCallback } from 'react'; +import type { NavigateToAppOptions } from '@kbn/core-application-browser'; import { useKibana } from './use_kibana'; export const useNavigateToApp = () => { @@ -14,7 +15,12 @@ export const useNavigateToApp = () => { } = useKibana(); const navigateToApp = useCallback( - (appId: string) => { + (appId: string, options?: NavigateToAppOptions) => { + if (options?.path) { + application?.navigateToApp(appId, options); + return; + } + const appUrl = chrome?.navLinks.get(appId)?.url; if (appUrl) { diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/assets/mcp_endpoint.svg b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/assets/mcp_endpoint.svg deleted file mode 100644 index 7bca33bd55872..0000000000000 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/assets/mcp_endpoint.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/types.ts b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/types.ts index c621e2eda9edf..7894bdf023863 100644 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/types.ts +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/public/types.ts @@ -10,6 +10,7 @@ import type { DataSourcesRegistryPluginSetup } from '@kbn/data-sources-registry- import type { CloudStart } from '@kbn/cloud-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface WorkplaceAIAppPluginSetup {} @@ -24,6 +25,7 @@ export interface WorkplaceAIAppPluginSetupDependencies { export interface WorkplaceAIAppPluginStartDependencies { inference: InferencePublicStart; spaces: SpacesPluginStart; + triggersActionsUi: TriggersAndActionsUIPublicPluginStart; cloud?: CloudStart; share?: SharePluginStart; } diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/server/plugin.ts b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/server/plugin.ts index 26eb04f93a29e..06890e95fac85 100644 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/server/plugin.ts +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/server/plugin.ts @@ -12,7 +12,6 @@ import type { PluginInitializerContext, LoggerFactory, } from '@kbn/core/server'; -import { registerWorkplaceAIApiKeysRoutes } from '@kbn/workplaceai-api-keys-server'; import { registerRoutes } from './routes'; import { registerFeatures } from './features'; import type { InternalServices } from './services/types'; @@ -63,8 +62,6 @@ export class WorkplaceAIAppPlugin }, }); - registerWorkplaceAIApiKeysRoutes(router, this.loggerFactory.get('api-keys')); - registerFeatures({ features: setupDeps.features }); // Register custom data types with the data sources registry diff --git a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/tsconfig.json b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/tsconfig.json index 3d0f9bef71263..d5ccf3b31e5d5 100644 --- a/x-pack/solutions/workplaceai/plugins/workplace_ai_app/tsconfig.json +++ b/x-pack/solutions/workplaceai/plugins/workplace_ai_app/tsconfig.json @@ -29,8 +29,6 @@ "@kbn/logging", "@kbn/data-sources-registry-plugin", "@kbn/react-query", - "@kbn/workplaceai-api-keys-components", - "@kbn/workplaceai-api-keys-server", "@kbn/deeplinks-agent-builder", "@kbn/deeplinks-data-connectors", "@kbn/deeplinks-analytics", @@ -39,7 +37,11 @@ "@kbn/cloud", "@kbn/cloud-plugin", "@kbn/share-plugin", - "@kbn/spaces-plugin" + "@kbn/spaces-plugin", + "@kbn/triggers-actions-ui-plugin", + "@kbn/elastic-assistant", + "@kbn/ai-assistant-connector-selector-action", + "@kbn/core-application-browser" ], "exclude": [ "target/**/*", diff --git a/yarn.lock b/yarn.lock index 37bc30b8403f6..3682d61b56b37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9440,14 +9440,6 @@ version "0.0.0" uid "" -"@kbn/workplaceai-api-keys-components@link:x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-components": - version "0.0.0" - uid "" - -"@kbn/workplaceai-api-keys-server@link:x-pack/solutions/workplaceai/packages/kbn-workplaceai-api-keys-server": - version "0.0.0" - uid "" - "@kbn/workspaces@link:src/platform/packages/shared/kbn-workspaces": version "0.0.0" uid ""