diff --git a/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx b/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx
index 8cb83176a6591..cd80b2489012e 100644
--- a/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx
+++ b/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx
@@ -43,6 +43,7 @@ function entryToDisplaylistItem(entry: ConfigEntryView): { description: string;
interface ConnectorConfigurationProps {
connector: Connector;
hasPlatinumLicense: boolean;
+ isDisabled?: boolean;
isLoading: boolean;
saveConfig: (configuration: Record) => void;
saveAndSync?: (configuration: Record) => void;
@@ -89,6 +90,7 @@ export const ConnectorConfigurationComponent: FC<
children,
connector,
hasPlatinumLicense,
+ isDisabled,
isLoading,
saveConfig,
saveAndSync,
@@ -207,6 +209,7 @@ export const ConnectorConfigurationComponent: FC<
data-test-subj="entSearchContent-connector-configuration-editConfiguration"
data-telemetry-id="entSearchContent-connector-overview-configuration-editConfiguration"
onClick={() => setIsEditing(!isEditing)}
+ isDisabled={isDisabled}
>
{i18n.translate(
'searchConnectors.configurationConnector.config.editButton.title',
diff --git a/packages/kbn-search-connectors/components/scheduling/connector_scheduling.tsx b/packages/kbn-search-connectors/components/scheduling/connector_scheduling.tsx
index 3d8ea94b3599a..62521b3e2b3fa 100644
--- a/packages/kbn-search-connectors/components/scheduling/connector_scheduling.tsx
+++ b/packages/kbn-search-connectors/components/scheduling/connector_scheduling.tsx
@@ -66,6 +66,7 @@ interface ConnectorContentSchedulingProps {
hasPlatinumLicense: boolean;
hasChanges: boolean;
hasIngestionError: boolean;
+ isDisabled?: boolean;
setHasChanges: (changes: boolean) => void;
shouldShowAccessControlSync: boolean;
shouldShowIncrementalSync: boolean;
@@ -81,6 +82,7 @@ export const ConnectorSchedulingComponent: React.FC
{shouldShowIncrementalSync && (
@@ -153,6 +156,7 @@ export const ConnectorSchedulingComponent: React.FC
)}
@@ -186,6 +190,7 @@ export const ConnectorSchedulingComponent: React.FC
diff --git a/packages/kbn-search-connectors/components/scheduling/full_content.tsx b/packages/kbn-search-connectors/components/scheduling/full_content.tsx
index de85f8fb2e4a9..3ec1fd4ab9e49 100644
--- a/packages/kbn-search-connectors/components/scheduling/full_content.tsx
+++ b/packages/kbn-search-connectors/components/scheduling/full_content.tsx
@@ -29,6 +29,7 @@ export interface ConnectorContentSchedulingProps {
dataTelemetryIdPrefix: string;
hasPlatinumLicense?: boolean;
hasSyncTypeChanges: boolean;
+ isDisabled?: boolean;
setHasChanges: (hasChanges: boolean) => void;
setHasSyncTypeChanges: (state: boolean) => void;
type: SyncJobType;
@@ -104,6 +105,7 @@ export const ConnectorContentScheduling: React.FC
@@ -217,7 +221,7 @@ export const ConnectorContentScheduling: React.FC {
setScheduling({
diff --git a/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx b/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx
index 296118410f868..38f880ec5298b 100644
--- a/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/api_key/api_key.tsx
@@ -20,8 +20,6 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { css } from '@emotion/react';
-import { ApiKey } from '@kbn/security-plugin-types-common';
-import { useQuery } from '@tanstack/react-query';
import React, { useEffect, useState } from 'react';
import { ApiKeySelectableTokenField } from '@kbn/security-api-key-management';
import {
@@ -32,6 +30,7 @@ import { useKibanaServices } from '../../hooks/use_kibana';
import { MANAGEMENT_API_KEYS } from '../../../../common/routes';
import { CreateApiKeyFlyout } from './create_api_key_flyout';
import './api_key.scss';
+import { useGetApiKeys } from '../../hooks/api/use_api_key';
function isCreatedResponse(
value: SecurityCreateApiKeyResponse | SecurityUpdateApiKeyResponse
@@ -45,15 +44,16 @@ function isCreatedResponse(
export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: string) => void }) => {
const { http, user } = useKibanaServices();
const [isFlyoutOpen, setIsFlyoutOpen] = useState(false);
- const { data } = useQuery({
- queryKey: ['apiKey'],
- queryFn: () => http.fetch<{ apiKeys: ApiKey[] }>('/internal/serverless_search/api_keys'),
- });
+ const { data } = useGetApiKeys();
+
const [apiKey, setApiKey] = useState(undefined);
const saveApiKey = (value: SecurityCreateApiKeyResponse) => {
setApiKey(value);
};
+ // Prevent flickering in the most common case of having access to manage api keys
+ const canManageOwnApiKey = !data || data.canManageOwnApiKey;
+
useEffect(() => {
if (apiKey) {
setClientApiKey(apiKey.encoded);
@@ -101,7 +101,7 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri
) : (
-
+
{i18n.translate('xpack.serverlessSearch.apiKey.panel.title', {
@@ -117,6 +117,16 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri
})}
+ {!canManageOwnApiKey && (
+ <>
+
+ {i18n.translate('xpack.serverlessSearch.apiKey.panel.noUserPrivileges', {
+ defaultMessage: "You don't have access to manage API keys",
+ })}
+
+
+ >
+ )}
@@ -127,6 +137,7 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri
size="s"
fill
onClick={() => setIsFlyoutOpen(true)}
+ disabled={!canManageOwnApiKey}
data-test-subj="new-api-key-button"
aria-label={i18n.translate(
'xpack.serverlessSearch.apiKey.newButton.ariaLabel',
@@ -143,24 +154,29 @@ export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: stri
-
-
-
- {i18n.translate('xpack.serverlessSearch.apiKey.manageLabel', {
- defaultMessage: 'Manage',
- })}
-
-
-
+ {canManageOwnApiKey && (
+
+
+
+ {i18n.translate('xpack.serverlessSearch.apiKey.manageLabel', {
+ defaultMessage: 'Manage',
+ })}
+
+
+
+ )}
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/conector_scheduling_tab/connector_scheduling.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/conector_scheduling_tab/connector_scheduling.tsx
index 12961bfc4a093..f8c48cbb3c8ab 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors/conector_scheduling_tab/connector_scheduling.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/conector_scheduling_tab/connector_scheduling.tsx
@@ -10,26 +10,33 @@ import { ConnectorSchedulingComponent } from '@kbn/search-connectors/components/
import { useConnectorScheduling } from '../../../hooks/api/use_update_connector_scheduling';
interface ConnectorSchedulingPanels {
+ canManageConnectors: boolean;
connector: Connector;
}
-export const ConnectorScheduling: React.FC = ({ connector }) => {
+export const ConnectorScheduling: React.FC = ({
+ canManageConnectors,
+ connector,
+}) => {
const [hasChanges, setHasChanges] = useState(false);
const { isLoading, mutate } = useConnectorScheduling(connector.id);
const hasIncrementalSyncFeature = connector?.features?.incremental_sync ?? false;
const shouldShowIncrementalSync =
hasIncrementalSyncFeature && (connector?.features?.incremental_sync?.enabled ?? false);
return (
-
+ <>
+
+ >
);
};
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/api_key_panel.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/api_key_panel.tsx
index eb1f4aba2ec2d..0ee9e3c638528 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/api_key_panel.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/api_key_panel.tsx
@@ -23,11 +23,13 @@ import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { OPTIONAL_LABEL } from '../../../../../common/i18n_string';
import { useCreateApiKey } from '../../../hooks/api/use_create_api_key';
+import { useGetApiKeys } from '../../../hooks/api/use_api_key';
interface ApiKeyPanelProps {
connector: Connector;
}
export const ApiKeyPanel: React.FC = ({ connector }) => {
const { data, isLoading, mutate } = useCreateApiKey();
+ const { data: apiKeysData } = useGetApiKeys();
return (
@@ -59,7 +61,7 @@ export const ApiKeyPanel: React.FC = ({ connector }) => {
= ({ connector }) => {
+export const ConnectorConfigFields: React.FC = ({
+ connector,
+ isDisabled,
+}) => {
const { data, isLoading, isSuccess, mutate, reset } = useEditConnectorConfiguration(connector.id);
const { queryKey } = useConnector(connector.id);
const queryClient = useQueryClient();
@@ -53,6 +57,7 @@ export const ConnectorConfigFields: React.FC = ({ co
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_panels.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_panels.tsx
index e07874a4676e4..93e3881020b27 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_panels.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_config_panels.tsx
@@ -16,10 +16,12 @@ import { ConnectionDetails } from './connection_details_panel';
import { ConnectorIndexnamePanel } from './connector_index_name_panel';
interface ConnectorConfigurationPanels {
+ canManageConnectors: boolean;
connector: Connector;
}
export const ConnectorConfigurationPanels: React.FC = ({
+ canManageConnectors,
connector,
}) => {
const { data, isLoading, isSuccess, mutate, reset } = useEditConnectorConfiguration(connector.id);
@@ -37,6 +39,7 @@ export const ConnectorConfigurationPanels: React.FC
-
+
= ({ connector }) => {
+ const { data } = useConnectors();
+ const { canManageConnectors } = data || { canManageConnectors: false };
const [currentStep, setCurrentStep] = useState('link');
useEffect(() => {
let step: ConnectorConfigurationStep = 'link';
@@ -99,7 +102,9 @@ export const ConnectorConfiguration: React.FC = ({
const tabs: EuiTabbedContentTab[] = [
{
- content: ,
+ content: (
+
+ ),
id: 'overview',
name: OVERVIEW_LABEL,
},
@@ -107,7 +112,10 @@ export const ConnectorConfiguration: React.FC = ({
content: (
<>
-
+
>
),
id: 'configuration',
@@ -117,7 +125,7 @@ export const ConnectorConfiguration: React.FC = ({
content: (
<>
-
+
>
),
id: 'scheduling',
@@ -140,8 +148,12 @@ export const ConnectorConfiguration: React.FC = ({
status={connector.status}
/>
)}
- {currentStep === 'configure' && }
- {currentStep === 'connect' && }
+ {currentStep === 'configure' && (
+
+ )}
+ {currentStep === 'connect' && (
+
+ )}
);
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name.tsx
index a421af47a0a79..153c41c0332e9 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name.tsx
@@ -32,9 +32,13 @@ import { docLinks } from '../../../../../common/doc_links';
import { DEFAULT_INGESTION_PIPELINE } from '../../../../../common';
interface ConnectorIndexNameProps {
connector: Connector;
+ isDisabled?: boolean;
}
-export const ConnectorIndexName: React.FC = ({ connector }) => {
+export const ConnectorIndexName: React.FC = ({
+ connector,
+ isDisabled,
+}) => {
const { http } = useKibanaServices();
const queryClient = useQueryClient();
const { queryKey } = useConnector(connector.id);
@@ -88,6 +92,7 @@ export const ConnectorIndexName: React.FC = ({ connecto
setNewIndexname(name)}
/>
@@ -145,7 +150,7 @@ export const ConnectorIndexName: React.FC = ({ connecto
mutate({ inputName: newIndexName, sync: false })}
>
@@ -162,7 +167,7 @@ export const ConnectorIndexName: React.FC = ({ connecto
!(
isValidIndexName(newIndexName) &&
[ConnectorStatus.CONFIGURED, ConnectorStatus.CONNECTED].includes(connector.status)
- )
+ ) || isDisabled
}
fill
isLoading={isLoading}
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name_panel.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name_panel.tsx
index ac472d9a4b440..3f4a51487a52e 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name_panel.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_index_name_panel.tsx
@@ -17,9 +17,13 @@ import { ConnectorIndexNameForm } from './connector_index_name_form';
interface ConnectorIndexNamePanelProps {
connector: Connector;
+ canManageConnectors: boolean;
}
-export const ConnectorIndexnamePanel: React.FC = ({ connector }) => {
+export const ConnectorIndexnamePanel: React.FC = ({
+ canManageConnectors,
+ connector,
+}) => {
const { http } = useKibanaServices();
const { data, isLoading, isSuccess, mutate, reset } = useMutation({
mutationFn: async (inputName: string) => {
@@ -48,7 +52,7 @@ export const ConnectorIndexnamePanel: React.FC = (
return (
<>
setNewIndexName(name)}
/>
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_overview.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_overview.tsx
index a4d79759d71cb..7a3d07eb5022b 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_overview.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_overview.tsx
@@ -21,10 +21,14 @@ import { useKibanaServices } from '../../../hooks/use_kibana';
import { SyncScheduledCallOut } from './sync_scheduled_callout';
interface ConnectorOverviewProps {
+ canManageConnectors: boolean;
connector: Connector;
}
-export const ConnectorOverview: React.FC = ({ connector }) => {
+export const ConnectorOverview: React.FC = ({
+ canManageConnectors,
+ connector,
+}) => {
const { http } = useKibanaServices();
const queryClient = useQueryClient();
const { queryKey } = useConnector(connector.id);
@@ -64,9 +68,11 @@ export const ConnectorOverview: React.FC = ({ connector
mutate()}
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_privileges_callout.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_privileges_callout.tsx
new file mode 100644
index 0000000000000..d2b1d7196cffb
--- /dev/null
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_config/connector_privileges_callout.tsx
@@ -0,0 +1,37 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { EuiCallOut, EuiSpacer } from '@elastic/eui';
+import React from 'react';
+import { useConnectors } from '../../../hooks/api/use_connectors';
+
+export const ConnectorPrivilegesCallout: React.FC = () => {
+ const { data } = useConnectors();
+ if (!data || (data.canManageConnectors && data.canReadConnectors)) {
+ return null;
+ }
+ const calloutTitle = i18n.translate('xpack.serverlessSearch.connectors.noPrivilegesTitle', {
+ defaultMessage: 'Insufficient access',
+ });
+ return (
+ <>
+
+ {data.canReadConnectors
+ ? i18n.translate('xpack.serverlessSearch.connectors.noManagePrivileges', {
+ defaultMessage:
+ 'You have read-only access to connectors. Contact your administrator for elevated privileges.',
+ })
+ : i18n.translate('xpack.serverlessSearch.connectors.noPrivileges', {
+ defaultMessage:
+ "You don't have access to connectors. Contact your administrator for access.",
+ })}
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connectors_table.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connectors_table.tsx
index 6f8dbd0edb4bc..4e559c74e8c87 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors/connectors_table.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connectors_table.tsx
@@ -211,7 +211,12 @@ export const ConnectorsTable: React.FC = () => {
onClick: (connector: Connector) => copyToClipboard(connector.id),
},
{
- render: (connector: Connector) => ,
+ render: (connector: Connector) => (
+
+ ),
},
],
name: i18n.translate('xpack.serverlessSearch.connectors.actionsLabel', {
@@ -287,7 +292,10 @@ export const ConnectorsTable: React.FC = () => {
);
};
-const DeleteConnectorModalAction: React.FC<{ connector: Connector }> = ({ connector }) => {
+const DeleteConnectorModalAction: React.FC<{ connector: Connector; disabled: boolean }> = ({
+ connector,
+ disabled,
+}) => {
const [modalIsOpen, setModalIsOpen] = useState(false);
return (
@@ -301,6 +309,7 @@ const DeleteConnectorModalAction: React.FC<{ connector: Connector }> = ({ connec
)}
setModalIsOpen(true)}
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_connector.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_connector.tsx
index 2932ffc7bf79a..a8c9ef2cd52eb 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_connector.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_connector.tsx
@@ -33,10 +33,14 @@ import { EditDescription } from './edit_description';
import { DeleteConnectorModal } from './delete_connector_modal';
import { ConnectorConfiguration } from './connector_config/connector_configuration';
import { useConnector } from '../../hooks/api/use_connector';
+import { useConnectors } from '../../hooks/api/use_connectors';
+import { ConnectorPrivilegesCallout } from './connector_config/connector_privileges_callout';
export const EditConnector: React.FC = () => {
const [deleteModalIsOpen, setDeleteModalIsOpen] = useState(false);
const [menuIsOpen, setMenuIsOpen] = useState(false);
+ const { data: connectorsData } = useConnectors();
+ const isDisabled = !connectorsData?.canManageConnectors;
const { id } = useParams<{ id: string }>();
@@ -47,7 +51,7 @@ export const EditConnector: React.FC = () => {
const { data, isLoading } = useConnector(id);
- if (isLoading) {
+ if (!data || isLoading) {
{
{CONNECTOR_LABEL}
-
+
{deleteModalIsOpen && (
@@ -142,6 +146,7 @@ export const EditConnector: React.FC = () => {
},
{
name: DELETE_CONNECTOR_LABEL,
+ disabled: isDisabled,
icon: 'trash',
onClick: () => {
setDeleteModalIsOpen(true);
@@ -157,12 +162,13 @@ export const EditConnector: React.FC = () => {
+
-
+
-
+
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_description.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_description.tsx
index 1749e1673e269..76f22f36f02af 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_description.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_description.tsx
@@ -25,10 +25,11 @@ import { useKibanaServices } from '../../hooks/use_kibana';
import { useConnector } from '../../hooks/api/use_connector';
interface EditDescriptionProps {
+ isDisabled?: boolean;
connector: Connector;
}
-export const EditDescription: React.FC = ({ connector }) => {
+export const EditDescription: React.FC = ({ connector, isDisabled }) => {
const [isEditing, setIsEditing] = useState(false);
const [newDescription, setNewDescription] = useState(connector.description || '');
const { http } = useKibanaServices();
@@ -66,15 +67,17 @@ export const EditDescription: React.FC = ({ connector }) =
defaultMessage: 'Description',
})}
labelAppend={
-
- setIsEditing(true)}
- role="button"
- >
- {EDIT_LABEL}
-
-
+ isDisabled ? undefined : (
+
+ setIsEditing(true)}
+ role="button"
+ >
+ {EDIT_LABEL}
+
+
+ )
}
fullWidth
>
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_name.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_name.tsx
index b81fc51b07bcf..2fccd9554abaf 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors/edit_name.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/edit_name.tsx
@@ -26,10 +26,11 @@ import { useKibanaServices } from '../../hooks/use_kibana';
import { useConnector } from '../../hooks/api/use_connector';
interface EditNameProps {
+ isDisabled?: boolean;
connector: Connector;
}
-export const EditName: React.FC = ({ connector }) => {
+export const EditName: React.FC = ({ connector, isDisabled }) => {
const [isEditing, setIsEditing] = useState(false);
const [newName, setNewName] = useState(connector.name || CONNECTOR_LABEL);
const { http } = useKibanaServices();
@@ -77,6 +78,7 @@ export const EditName: React.FC = ({ connector }) => {
>
= ({ connector }) => {
+export const EditServiceType: React.FC = ({ connector, isDisabled }) => {
const { http } = useKibanaServices();
const connectorTypes = useConnectorTypes();
const queryClient = useQueryClient();
@@ -71,7 +72,7 @@ export const EditServiceType: React.FC = ({ connector }) =
>
mutate(event)}
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx
index 5a90a9b4ff0da..56c7a9aaf8155 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx
@@ -20,13 +20,16 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
+import { docLinks } from '../../../../common/doc_links';
import { useConnectorTypes } from '../../hooks/api/use_connector_types';
import { useCreateConnector } from '../../hooks/api/use_create_connector';
import { useAssetBasePath } from '../../hooks/use_asset_base_path';
+import { useConnectors } from '../../hooks/api/use_connectors';
export const EmptyConnectorsPrompt: React.FC = () => {
const connectorTypes = useConnectorTypes();
const { createConnector, isLoading } = useCreateConnector();
+ const { data } = useConnectors();
const assetBasePath = useAssetBasePath();
const connectorsPath = assetBasePath + '/connectors.svg';
@@ -94,7 +97,7 @@ export const EmptyConnectorsPrompt: React.FC = () => {
source: (
{i18n.translate(
'xpack.serverlessSearch.connectorsEmpty.sourceLabel',
@@ -105,7 +108,7 @@ export const EmptyConnectorsPrompt: React.FC = () => {
docker: (
{i18n.translate(
'xpack.serverlessSearch.connectorsEmpty.dockerLabel',
@@ -167,6 +170,7 @@ export const EmptyConnectorsPrompt: React.FC = () => {
createConnector()}
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors_callout.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors_callout.tsx
index 52fb878d4b619..625973a1f377a 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors_callout.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors_callout.tsx
@@ -10,9 +10,11 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { useCreateConnector } from '../hooks/api/use_create_connector';
+import { useConnectors } from '../hooks/api/use_connectors';
export const ConnectorsCallout = () => {
const { createConnector, isLoading } = useCreateConnector();
+ const { data } = useConnectors();
return (
{
createConnector()}
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx
index ede574abba12b..9f7f92b031e8f 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx
@@ -19,9 +19,12 @@ import { GithubLink } from '@kbn/search-api-panels';
import React from 'react';
import { useCreateConnector } from '../hooks/api/use_create_connector';
+import { useConnectors } from '../hooks/api/use_connectors';
export const ConnectorIngestionPanel: React.FC<{ assetBasePath: string }> = ({ assetBasePath }) => {
const { createConnector } = useCreateConnector();
+ const { data } = useConnectors();
+
return (
@@ -49,19 +52,21 @@ export const ConnectorIngestionPanel: React.FC<{ assetBasePath: string }> = ({ a
-
- createConnector()}
- >
- {i18n.translate(
- 'xpack.serverlessSearch.ingestData.alternativeOptions.setupConnectorLabel',
- {
- defaultMessage: 'Set up a connector',
- }
- )}
-
-
+ {Boolean(data?.canManageConnectors) && (
+
+ createConnector()}
+ >
+ {i18n.translate(
+ 'xpack.serverlessSearch.ingestData.alternativeOptions.setupConnectorLabel',
+ {
+ defaultMessage: 'Set up a connector',
+ }
+ )}
+
+
+ )}
{
const { data, isLoading: connectorsLoading } = useConnectors();
@@ -35,6 +37,8 @@ export const ConnectorsOverview = () => {
[consolePlugin]
);
+ const canManageConnectors = !data || data.canManageConnectors;
+
return (
{
{
data-test-subj="serverlessSearchConnectorsOverviewLink"
external
target="_blank"
- href={'TODO TODO'}
+ href={docLinks.connectors}
>
{LEARN_MORE_LABEL}
@@ -110,15 +115,14 @@ export const ConnectorsOverview = () => {
- {connectorsLoading || (data?.connectors || []).length > 0 ? (
-
+
+
+ {connectorsLoading || (data?.connectors || []).length > 0 ? (
-
- ) : (
-
+ ) : (
-
- )}
+ )}
+
{embeddableConsole}
);
diff --git a/x-pack/plugins/serverless_search/public/application/components/pipeline_manage_button.tsx b/x-pack/plugins/serverless_search/public/application/components/pipeline_manage_button.tsx
index 15337cf6a7f30..14ec93402324b 100644
--- a/x-pack/plugins/serverless_search/public/application/components/pipeline_manage_button.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/pipeline_manage_button.tsx
@@ -11,21 +11,24 @@ import { EuiSpacer, EuiText, EuiButtonEmpty } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useKibanaServices } from '../hooks/use_kibana';
+import { useIngestPipelines } from '../hooks/api/use_ingest_pipelines';
export const PipelineManageButton: React.FC = () => {
const { http } = useKibanaServices();
+ const { data } = useIngestPipelines();
return (
<>
{i18n.translate('xpack.serverlessSearch.pipeline.description.manageButtonLabel', {
- defaultMessage: 'Manage pipeline',
+ defaultMessage: 'Manage pipelines',
})}
diff --git a/x-pack/plugins/serverless_search/public/application/components/pipeline_overview_button.tsx b/x-pack/plugins/serverless_search/public/application/components/pipeline_overview_button.tsx
index cc01bb538600a..d9be14209cb7e 100644
--- a/x-pack/plugins/serverless_search/public/application/components/pipeline_overview_button.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/pipeline_overview_button.tsx
@@ -11,9 +11,11 @@ import { EuiSpacer, EuiText, EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useKibanaServices } from '../hooks/use_kibana';
+import { useIngestPipelines } from '../hooks/api/use_ingest_pipelines';
export const PipelineOverviewButton: React.FC = () => {
const { http } = useKibanaServices();
+ const { data } = useIngestPipelines();
return (
<>
@@ -21,6 +23,7 @@ export const PipelineOverviewButton: React.FC = () => {
diff --git a/x-pack/plugins/serverless_search/public/application/hooks/api/use_api_key.tsx b/x-pack/plugins/serverless_search/public/application/hooks/api/use_api_key.tsx
new file mode 100644
index 0000000000000..cb0dce762ad14
--- /dev/null
+++ b/x-pack/plugins/serverless_search/public/application/hooks/api/use_api_key.tsx
@@ -0,0 +1,21 @@
+/*
+ * 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 { ApiKey } from '@kbn/security-plugin-types-common';
+import { useQuery } from '@tanstack/react-query';
+import { useKibanaServices } from '../use_kibana';
+
+export const useGetApiKeys = () => {
+ const { http } = useKibanaServices();
+ return useQuery({
+ queryKey: ['apiKey'],
+ queryFn: () =>
+ http.fetch<{ apiKeys: ApiKey[]; canManageOwnApiKey: boolean }>(
+ '/internal/serverless_search/api_keys'
+ ),
+ });
+};
diff --git a/x-pack/plugins/serverless_search/public/application/hooks/api/use_connectors.tsx b/x-pack/plugins/serverless_search/public/application/hooks/api/use_connectors.tsx
index f6de4223cfdce..2c880f3923149 100644
--- a/x-pack/plugins/serverless_search/public/application/hooks/api/use_connectors.tsx
+++ b/x-pack/plugins/serverless_search/public/application/hooks/api/use_connectors.tsx
@@ -14,6 +14,10 @@ export const useConnectors = () => {
return useQuery({
queryKey: ['fetchConnectors'],
queryFn: () =>
- http.fetch<{ connectors: Connector[] }>('/internal/serverless_search/connectors'),
+ http.fetch<{
+ connectors: Connector[];
+ canManageConnectors: boolean;
+ canReadConnectors: boolean;
+ }>('/internal/serverless_search/connectors'),
});
};
diff --git a/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx b/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx
index bd7d4292cfe18..5507ff16f7705 100644
--- a/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx
+++ b/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx
@@ -14,7 +14,7 @@ export const useIngestPipelines = () => {
return useQuery({
queryKey: ['fetchIngestPipelines'],
queryFn: async () =>
- http.fetch>(
+ http.fetch<{ pipelines: IngestGetPipelineResponse; canManagePipelines: boolean }>(
`/internal/serverless_search/ingest_pipelines`
),
});
diff --git a/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx b/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx
index df73a27f9097d..2c659f9e3fe44 100644
--- a/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx
+++ b/x-pack/plugins/serverless_search/public/application/hooks/use_kibana.tsx
@@ -12,12 +12,14 @@ import type { SharePluginStart } from '@kbn/share-plugin/public';
import { useKibana as useKibanaBase } from '@kbn/kibana-react-plugin/public';
import { AuthenticatedUser } from '@kbn/security-plugin/common';
import { SearchConnectorsPluginStart } from '@kbn/search-connectors-plugin/public';
+import { SecurityPluginStart } from '@kbn/security-plugin-types-public';
export interface ServerlessSearchContext {
cloud: CloudStart;
console: ConsolePluginStart;
history: AppMountParameters['history'];
searchConnectors?: SearchConnectorsPluginStart;
+ security: SecurityPluginStart;
share: SharePluginStart;
user?: AuthenticatedUser;
}
diff --git a/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts b/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts
index 3d89d104eaa1c..a4f702a4d9d2b 100644
--- a/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts
+++ b/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts
@@ -7,6 +7,7 @@
import { schema } from '@kbn/config-schema';
import { RouteDependencies } from '../plugin';
+import { errorHandler } from '../utils/error_handler';
export const registerApiKeyRoutes = ({ logger, router, getSecurity }: RouteDependencies) => {
router.get(
@@ -14,20 +15,32 @@ export const registerApiKeyRoutes = ({ logger, router, getSecurity }: RouteDepen
path: '/internal/serverless_search/api_keys',
validate: {},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const core = await context.core;
const { client } = core.elasticsearch;
const user = core.security.authc.getCurrentUser();
if (user) {
- const apiKeys = await client.asCurrentUser.security.getApiKey({ username: user.username });
- const validKeys = apiKeys.api_keys.filter(({ invalidated }) => !invalidated);
- return response.ok({ body: { apiKeys: validKeys } });
+ const privileges = await client.asCurrentUser.security.hasPrivileges({
+ cluster: ['manage_own_api_key'],
+ });
+ const canManageOwnApiKey = privileges?.cluster.manage_own_api_key;
+
+ try {
+ const apiKeys = await client.asCurrentUser.security.getApiKey({
+ username: user.username,
+ });
+
+ const validKeys = apiKeys.api_keys.filter(({ invalidated }) => !invalidated);
+ return response.ok({ body: { apiKeys: validKeys, canManageOwnApiKey } });
+ } catch {
+ return response.ok({ body: { apiKeys: [], canManageOwnApiKey } });
+ }
}
return response.customError({
statusCode: 502,
body: 'Could not retrieve current user, security plugin is not ready',
});
- }
+ })
);
router.post(
@@ -37,7 +50,7 @@ export const registerApiKeyRoutes = ({ logger, router, getSecurity }: RouteDepen
body: schema.any(),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const security = await getSecurity();
const result = await security.authc.apiKeys.create(request, request.body);
@@ -49,6 +62,6 @@ export const registerApiKeyRoutes = ({ logger, router, getSecurity }: RouteDepen
statusCode: 502,
body: 'Could not retrieve current user, security plugin is not ready',
});
- }
+ })
);
};
diff --git a/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts b/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts
index c57610dd9523a..09896a1808e4d 100644
--- a/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts
+++ b/x-pack/plugins/serverless_search/server/routes/connectors_routes.ts
@@ -22,24 +22,32 @@ import {
} from '@kbn/search-connectors';
import { DEFAULT_INGESTION_PIPELINE } from '../../common';
import { RouteDependencies } from '../plugin';
+import { errorHandler } from '../utils/error_handler';
-export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) => {
+export const registerConnectorsRoutes = ({ logger, http, router }: RouteDependencies) => {
router.get(
{
path: '/internal/serverless_search/connectors',
validate: {},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
- const connectors = await fetchConnectors(client.asCurrentUser);
+ const privileges = await client.asCurrentUser.security.hasPrivileges({
+ index: [{ names: ['.elastic-connectors'], privileges: ['read', 'write'] }],
+ });
+ const canManageConnectors = privileges.index['.elastic-connectors'].write;
+ const canReadConnectors = privileges.index['.elastic-connectors'].read;
+
+ const connectors = canReadConnectors ? await fetchConnectors(client.asCurrentUser) : [];
return response.ok({
body: {
connectors,
+ canManageConnectors,
+ canReadConnectors,
},
- headers: { 'content-type': 'application/json' },
});
- }
+ })
);
router.get(
@@ -51,7 +59,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const connector = await fetchConnectorById(client.asCurrentUser, request.params.connectorId);
@@ -63,7 +71,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
headers: { 'content-type': 'application/json' },
})
: response.notFound();
- }
+ })
);
router.post(
@@ -71,7 +79,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
path: '/internal/serverless_search/connectors',
validate: {},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const defaultPipeline: IngestPipelineParams = {
name: DEFAULT_INGESTION_PIPELINE,
@@ -92,7 +100,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
},
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
router.post(
@@ -107,7 +115,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const result = await updateConnectorNameAndDescription(
client.asCurrentUser,
@@ -123,7 +131,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
},
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
router.post(
@@ -138,7 +146,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const result = await updateConnectorNameAndDescription(
client.asCurrentUser,
@@ -154,7 +162,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
},
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
router.post(
@@ -169,7 +177,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
try {
const result = await updateConnectorIndexName(
@@ -186,7 +194,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
} catch (e) {
return response.conflict({ body: e });
}
- }
+ })
);
router.post(
@@ -201,7 +209,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const result = await updateConnectorServiceType(
client.asCurrentUser,
@@ -215,7 +223,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
},
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
router.delete(
@@ -227,7 +235,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const result = await deleteConnectorById(client.asCurrentUser, request.params.connectorId);
return response.ok({
@@ -236,7 +244,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
},
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
router.post(
@@ -254,7 +262,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const result = await updateConnectorConfiguration(
client.asCurrentUser,
@@ -266,7 +274,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
body: result,
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
router.post(
@@ -278,7 +286,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const result = await startConnectorSync(client.asCurrentUser, {
connectorId: request.params.connectorId,
@@ -288,7 +296,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
body: result,
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
router.get(
@@ -305,7 +313,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const result = await fetchSyncJobs(
client.asCurrentUser,
@@ -319,7 +327,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
body: result,
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
router.post(
{
@@ -335,7 +343,7 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
await updateConnectorScheduling(
client.asCurrentUser,
@@ -343,6 +351,6 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
request.body
);
return response.ok();
- }
+ })
);
};
diff --git a/x-pack/plugins/serverless_search/server/routes/indices_routes.ts b/x-pack/plugins/serverless_search/server/routes/indices_routes.ts
index 5c3e2187b1333..6b7ba424fde22 100644
--- a/x-pack/plugins/serverless_search/server/routes/indices_routes.ts
+++ b/x-pack/plugins/serverless_search/server/routes/indices_routes.ts
@@ -13,8 +13,9 @@ import { DEFAULT_DOCS_PER_PAGE } from '@kbn/search-index-documents/types';
import { fetchIndices } from '../lib/indices/fetch_indices';
import { fetchIndex } from '../lib/indices/fetch_index';
import { RouteDependencies } from '../plugin';
+import { errorHandler } from '../utils/error_handler';
-export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies) => {
+export const registerIndicesRoutes = ({ logger, router }: RouteDependencies) => {
router.get(
{
path: '/internal/serverless_search/indices',
@@ -26,7 +27,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const core = await context.core;
const client = core.elasticsearch.client.asCurrentUser;
const user = core.security.authc.getCurrentUser();
@@ -47,7 +48,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies
},
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
router.get(
@@ -59,7 +60,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const client = (await context.core).elasticsearch.client.asCurrentUser;
const result = await client.indices.get({
@@ -74,7 +75,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies
},
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
router.get(
@@ -86,7 +87,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const body = await fetchIndex(client.asCurrentUser, request.params.indexName);
return body
@@ -95,7 +96,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies
headers: { 'content-type': 'application/json' },
})
: response.notFound();
- }
+ })
);
router.post(
@@ -119,7 +120,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const client = (await context.core).elasticsearch.client.asCurrentUser;
const indexName = decodeURIComponent(request.params.index_name);
const searchQuery = request.body.searchQuery;
@@ -134,7 +135,7 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies
},
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
};
diff --git a/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts b/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts
index 0853540c66f04..349a637273135 100644
--- a/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts
+++ b/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts
@@ -6,23 +6,36 @@
*/
import { RouteDependencies } from '../plugin';
+import { errorHandler } from '../utils/error_handler';
-export const registerIngestPipelineRoutes = ({ router }: RouteDependencies) => {
+export const registerIngestPipelineRoutes = ({ logger, router }: RouteDependencies) => {
router.get(
{
path: '/internal/serverless_search/ingest_pipelines',
validate: {},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
+ const privileges = await client.asCurrentUser.security.hasPrivileges({
+ cluster: ['manage_pipeline'],
+ });
+
+ const canManagePipelines = privileges?.cluster.manage_pipeline;
+
+ if (!canManagePipelines) {
+ return response.ok({
+ body: { pipelines: {}, canManagePipelines: false },
+ });
+ }
const pipelines = await client.asCurrentUser.ingest.getPipeline();
return response.ok({
body: {
pipelines,
+ canManagePipelines,
},
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
};
diff --git a/x-pack/plugins/serverless_search/server/routes/mapping_routes.ts b/x-pack/plugins/serverless_search/server/routes/mapping_routes.ts
index bb6e22a1bd8fe..520de808b41c9 100644
--- a/x-pack/plugins/serverless_search/server/routes/mapping_routes.ts
+++ b/x-pack/plugins/serverless_search/server/routes/mapping_routes.ts
@@ -7,8 +7,9 @@
import { schema } from '@kbn/config-schema';
import { RouteDependencies } from '../plugin';
+import { errorHandler } from '../utils/error_handler';
-export const registerMappingRoutes = ({ router }: RouteDependencies) => {
+export const registerMappingRoutes = ({ logger, router }: RouteDependencies) => {
router.get(
{
path: '/internal/serverless_search/mappings/{index_name}',
@@ -18,7 +19,7 @@ export const registerMappingRoutes = ({ router }: RouteDependencies) => {
}),
},
},
- async (context, request, response) => {
+ errorHandler(logger)(async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const mapping = await client.asCurrentUser.indices.getMapping({
expand_wildcards: ['open'],
@@ -28,6 +29,6 @@ export const registerMappingRoutes = ({ router }: RouteDependencies) => {
body: mapping[request.params.index_name],
headers: { 'content-type': 'application/json' },
});
- }
+ })
);
};
diff --git a/x-pack/plugins/serverless_search/server/utils/error_handler.ts b/x-pack/plugins/serverless_search/server/utils/error_handler.ts
new file mode 100644
index 0000000000000..b4b3894125bdb
--- /dev/null
+++ b/x-pack/plugins/serverless_search/server/utils/error_handler.ts
@@ -0,0 +1,28 @@
+/*
+ * 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 { RequestHandlerWrapper } from '@kbn/core-http-server';
+import { KibanaServerError } from '@kbn/kibana-utils-plugin/common';
+import type { Logger } from '@kbn/logging';
+
+function isKibanaServerError(error: any): error is KibanaServerError {
+ return error.statusCode && error.message;
+}
+
+export const errorHandler: (logger: Logger) => RequestHandlerWrapper = (logger) => (handler) => {
+ return async (context, request, response) => {
+ try {
+ return await handler(context, request, response);
+ } catch (e) {
+ logger.error(e);
+ if (isKibanaServerError(e)) {
+ return response.customError({ statusCode: e.statusCode, body: e.message });
+ }
+ throw e;
+ }
+ };
+};
diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json
index cc3b7b073dcee..0f7a803a68f7d 100644
--- a/x-pack/plugins/serverless_search/tsconfig.json
+++ b/x-pack/plugins/serverless_search/tsconfig.json
@@ -52,5 +52,8 @@
"@kbn/search-inference-endpoints",
"@kbn/security-plugin-types-common",
"@kbn/search-indices",
+ "@kbn/core-http-server",
+ "@kbn/logging",
+ "@kbn/security-plugin-types-public",
]
}
diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_landing_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_landing_page.ts
index 3618fed58dcf3..4a9d858914dc6 100644
--- a/x-pack/test_serverless/functional/page_objects/svl_search_landing_page.ts
+++ b/x-pack/test_serverless/functional/page_objects/svl_search_landing_page.ts
@@ -75,7 +75,7 @@ export function SvlSearchLandingPageProvider({ getService }: FtrProviderContext)
},
pipeline: {
async createPipeline() {
- await testSubjects.click('create-a-pipeline-button');
+ await testSubjects.clickWhenNotDisabled('create-a-pipeline-button');
},
async expectNavigateToCreatePipelinePage() {
expect(await browser.getCurrentUrl()).contain(
@@ -83,7 +83,7 @@ export function SvlSearchLandingPageProvider({ getService }: FtrProviderContext)
);
},
async managePipeline() {
- await testSubjects.click('manage-pipeline-button');
+ await testSubjects.clickWhenNotDisabled('manage-pipeline-button');
},
async expectNavigateToManagePipelinePage() {
expect(await browser.getCurrentUrl()).contain('/app/management/ingest/ingest_pipelines');
diff --git a/x-pack/test_serverless/functional/test_suites/search/getting_started.ts b/x-pack/test_serverless/functional/test_suites/search/getting_started.ts
index f521a03ccde85..dac9b2d7dae94 100644
--- a/x-pack/test_serverless/functional/test_suites/search/getting_started.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/getting_started.ts
@@ -15,7 +15,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
describe('getting started', function () {
before(async () => {
- await pageObjects.svlCommonPage.loginAsViewer();
+ await pageObjects.svlCommonPage.loginAsAdmin();
});
it('has serverless side nav', async () => {