diff --git a/x-pack/plugins/searchprofiler/common/constants.ts b/x-pack/plugins/searchprofiler/common/constants.ts
index a50ed281c2bd0..0d586e9b1fb68 100644
--- a/x-pack/plugins/searchprofiler/common/constants.ts
+++ b/x-pack/plugins/searchprofiler/common/constants.ts
@@ -9,6 +9,8 @@ import { LicenseType } from '@kbn/licensing-plugin/common/types';
const basicLicense: LicenseType = 'basic';
+export const API_BASE_PATH = '/api/searchprofiler';
+
/** @internal */
export const PLUGIN = Object.freeze({
id: 'searchprofiler',
diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx
index a88f1040caa3a..d80cbc4f0394d 100644
--- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx
+++ b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/profile_query_editor.tsx
@@ -16,11 +16,12 @@ import {
EuiFlexGroup,
EuiSpacer,
EuiFlexItem,
+ EuiToolTip,
} from '@elastic/eui';
import { decompressFromEncodedURIComponent } from 'lz-string';
-import { useRequestProfile } from '../../hooks';
+import { useHasIndices, useRequestProfile } from '../../hooks';
import { useAppContext } from '../../contexts/app_context';
import { useProfilerActionContext } from '../../contexts/profiler_context';
import { Editor, type EditorProps } from './editor';
@@ -46,6 +47,8 @@ export const ProfileQueryEditor = memo(() => {
const { getLicenseStatus, notifications, location } = useAppContext();
+ const { data: indicesData, isLoading, error: indicesDataError } = useHasIndices();
+
const queryParams = new URLSearchParams(location.search);
const indexName = queryParams.get('index');
const searchProfilerQueryURI = queryParams.get('load_from');
@@ -86,6 +89,32 @@ export const ProfileQueryEditor = memo(() => {
);
const licenseEnabled = getLicenseStatus().valid;
+ const hasIndices = isLoading || indicesDataError ? false : indicesData?.hasIndices;
+
+ const isDisabled = !licenseEnabled || !hasIndices;
+ const tooltipContent = !licenseEnabled
+ ? i18n.translate('xpack.searchProfiler.formProfileButton.noLicenseTooltip', {
+ defaultMessage: 'You need an active license to use Search Profiler',
+ })
+ : i18n.translate('xpack.searchProfiler.formProfileButton.noIndicesTooltip', {
+ defaultMessage: 'You must have at least one index to use Search Profiler',
+ });
+
+ const button = (
+
+
+ {i18n.translate('xpack.searchProfiler.formProfileButtonLabel', {
+ defaultMessage: 'Profile',
+ })}
+
+
+ );
+
return (
{/* Form */}
@@ -135,18 +164,13 @@ export const ProfileQueryEditor = memo(() => {
- handleProfileClick()}
- >
-
- {i18n.translate('xpack.searchProfiler.formProfileButtonLabel', {
- defaultMessage: 'Profile',
- })}
-
-
+ {isDisabled ? (
+
+ {button}
+
+ ) : (
+ button
+ )}
diff --git a/x-pack/plugins/searchprofiler/public/application/hooks/index.ts b/x-pack/plugins/searchprofiler/public/application/hooks/index.ts
index 9c1b3bfb8e9ed..156ad6bc8b163 100644
--- a/x-pack/plugins/searchprofiler/public/application/hooks/index.ts
+++ b/x-pack/plugins/searchprofiler/public/application/hooks/index.ts
@@ -6,3 +6,4 @@
*/
export { useRequestProfile } from './use_request_profile';
+export { useHasIndices } from './use_has_indices';
diff --git a/x-pack/plugins/searchprofiler/public/application/hooks/use_has_indices.ts b/x-pack/plugins/searchprofiler/public/application/hooks/use_has_indices.ts
new file mode 100644
index 0000000000000..43938d14a421f
--- /dev/null
+++ b/x-pack/plugins/searchprofiler/public/application/hooks/use_has_indices.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 { useRequest } from '@kbn/es-ui-shared-plugin/public';
+import { API_BASE_PATH } from '../../../common/constants';
+import { useAppContext } from '../contexts/app_context';
+
+interface ReturnValue {
+ hasIndices: boolean;
+}
+
+export const useHasIndices = () => {
+ const { http } = useAppContext();
+ return useRequest(http, {
+ path: `${API_BASE_PATH}/has_indices`,
+ method: 'get',
+ });
+};
diff --git a/x-pack/plugins/searchprofiler/server/routes/profile.ts b/x-pack/plugins/searchprofiler/server/routes/profile.ts
index 9e76bf0df96a1..7141a51c2c7f5 100644
--- a/x-pack/plugins/searchprofiler/server/routes/profile.ts
+++ b/x-pack/plugins/searchprofiler/server/routes/profile.ts
@@ -6,12 +6,13 @@
*/
import { schema } from '@kbn/config-schema';
+import { API_BASE_PATH } from '../../common/constants';
import { RouteDependencies } from '../types';
export const register = ({ router, getLicenseStatus, log }: RouteDependencies) => {
router.post(
{
- path: '/api/searchprofiler/profile',
+ path: `${API_BASE_PATH}/profile`,
validate: {
body: schema.object({
query: schema.object({}, { unknowns: 'allow' }),
@@ -56,6 +57,48 @@ export const register = ({ router, getLicenseStatus, log }: RouteDependencies) =
log.error(err);
const { statusCode, body: errorBody } = err;
+ return response.customError({
+ statusCode: statusCode || 500,
+ body: errorBody
+ ? {
+ message: errorBody.error?.reason,
+ attributes: errorBody,
+ }
+ : err,
+ });
+ }
+ }
+ );
+ router.get(
+ {
+ path: `${API_BASE_PATH}/has_indices`,
+ validate: false,
+ },
+ async (ctx, _request, response) => {
+ const currentLicenseStatus = getLicenseStatus();
+ if (!currentLicenseStatus.valid) {
+ return response.forbidden({
+ body: {
+ message: currentLicenseStatus.message!,
+ },
+ });
+ }
+
+ try {
+ const client = (await ctx.core).elasticsearch.client.asCurrentUser;
+ const resp = await client.cat.indices({ format: 'json' });
+
+ const hasIndices = resp.length > 0;
+
+ return response.ok({
+ body: {
+ hasIndices,
+ },
+ });
+ } catch (err) {
+ log.error(err);
+ const { statusCode, body: errorBody } = err;
+
return response.customError({
statusCode: statusCode || 500,
body: errorBody
diff --git a/x-pack/test/api_integration/apis/management/index_management/index.ts b/x-pack/test/api_integration/apis/management/index_management/index.ts
index 63ab1f3371941..e17da6cae3b6f 100644
--- a/x-pack/test/api_integration/apis/management/index_management/index.ts
+++ b/x-pack/test/api_integration/apis/management/index_management/index.ts
@@ -22,5 +22,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./enrich_policies'));
loadTestFile(require.resolve('./create_enrich_policy'));
loadTestFile(require.resolve('./data_enrichers'));
+ loadTestFile(require.resolve('./searchprofiler'));
});
}
diff --git a/x-pack/test/api_integration/apis/management/index_management/searchprofiler.ts b/x-pack/test/api_integration/apis/management/index_management/searchprofiler.ts
new file mode 100644
index 0000000000000..01c4347945118
--- /dev/null
+++ b/x-pack/test/api_integration/apis/management/index_management/searchprofiler.ts
@@ -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 expect from 'expect';
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+const API_BASE_PATH = '/api/searchprofiler';
+
+export default function ({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+
+ describe('Searchprofiler', function () {
+ it('Can retrive has indices', async () => {
+ const { body } = await supertest.get(`${API_BASE_PATH}/has_indices`).expect(200);
+ expect(body).toStrictEqual({ hasIndices: true });
+ });
+ });
+}
diff --git a/x-pack/test_serverless/functional/test_suites/common/dev_tools/search_profiler.ts b/x-pack/test_serverless/functional/test_suites/common/dev_tools/search_profiler.ts
index 979943ffa602c..169f266e0c296 100644
--- a/x-pack/test_serverless/functional/test_suites/common/dev_tools/search_profiler.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/dev_tools/search_profiler.ts
@@ -18,6 +18,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const PageObjects = getPageObjects(['svlCommonPage', 'common', 'searchProfiler']);
const retry = getService('retry');
const es = getService('es');
+ const browser = getService('browser');
describe('Search Profiler Editor', () => {
before(async () => {
@@ -81,6 +82,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
it('profiles a simple query', async () => {
+ await browser.refresh();
await PageObjects.searchProfiler.setIndexName(indexName);
await PageObjects.searchProfiler.setQuery(testQuery);