mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[APM] Add Experimental Mode for APM UI (#139553)
* experimental tab * moving apm specific settings * reverting and addin LazyField * one setting to rule them all * renaming tab * new setting option and flyout * flyout footer * refactoring * creating hook * removing utils * saving on kbn adv settings * saved object * refactoring * extracting apm experimental features * auto subs * removing auto-subscribe feature * fixing ci * removing common settings * new api to fetch labs items * renaming * renaming labs settings * fixing ci * handling exception * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * fixing ci * addressing pr comments Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
78f6244109
commit
ed6411e813
18 changed files with 482 additions and 42 deletions
|
@ -20,6 +20,10 @@ export { ComponentRegistry } from './component_registry';
|
|||
const LazyField = React.lazy(() => import('./management_app/components/field'));
|
||||
export { LazyField };
|
||||
|
||||
export { toEditableConfig } from './management_app/lib/to_editable_config';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new AdvancedSettingsPlugin();
|
||||
}
|
||||
|
||||
export type { FieldState } from './management_app/types';
|
||||
|
|
|
@ -518,6 +518,10 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
|
|||
type: 'boolean',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
'observability:apmLabsButton': {
|
||||
type: 'boolean',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
},
|
||||
'observability:apmProgressiveLoading': {
|
||||
type: 'keyword',
|
||||
_meta: { description: 'Non-default value of setting.' },
|
||||
|
|
|
@ -139,6 +139,7 @@ export interface UsageStats {
|
|||
'lens:useFieldExistenceSampling': boolean;
|
||||
'metrics:allowCheckingForFailedShards': boolean;
|
||||
'observability:apmOperationsTab': boolean;
|
||||
'observability:apmLabsButton': boolean;
|
||||
'observability:apmProgressiveLoading': string;
|
||||
'observability:apmServiceGroupMaxNumberOfServices': number;
|
||||
'observability:apmServiceInventoryOptimizedSorting': boolean;
|
||||
|
|
|
@ -8622,6 +8622,12 @@
|
|||
"description": "Non-default value of setting."
|
||||
}
|
||||
},
|
||||
"observability:apmLabsButton": {
|
||||
"type": "boolean",
|
||||
"_meta": {
|
||||
"description": "Non-default value of setting."
|
||||
}
|
||||
},
|
||||
"observability:apmProgressiveLoading": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
|
@ -10257,4 +10263,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,8 @@
|
|||
"triggersActionsUi",
|
||||
"share",
|
||||
"unifiedSearch",
|
||||
"dataViews"
|
||||
"dataViews",
|
||||
"advancedSettings"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"actions",
|
||||
|
@ -48,4 +49,4 @@
|
|||
"observability",
|
||||
"esUiShared"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 { EuiButton } from '@elastic/eui';
|
||||
import { LazyField } from '@kbn/advanced-settings-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
apmLabsButton,
|
||||
apmProgressiveLoading,
|
||||
apmServiceGroupMaxNumberOfServices,
|
||||
defaultApmServiceEnvironment,
|
||||
enableComparisonByDefault,
|
||||
enableInspectEsQueries,
|
||||
} from '@kbn/observability-plugin/common';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React from 'react';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useApmEditableSettings } from '../../../../hooks/use_apm_editable_settings';
|
||||
|
||||
const apmSettingsKeys = [
|
||||
enableComparisonByDefault,
|
||||
defaultApmServiceEnvironment,
|
||||
apmProgressiveLoading,
|
||||
apmServiceGroupMaxNumberOfServices,
|
||||
enableInspectEsQueries,
|
||||
apmLabsButton,
|
||||
];
|
||||
|
||||
export function GeneralSettings() {
|
||||
const { docLinks, notifications } = useApmPluginContext().core;
|
||||
const {
|
||||
handleFieldChange,
|
||||
settingsEditableConfig,
|
||||
unsavedChanges,
|
||||
saveAll,
|
||||
isSaving,
|
||||
} = useApmEditableSettings(apmSettingsKeys);
|
||||
|
||||
async function handleSave() {
|
||||
try {
|
||||
const reloadPage = Object.keys(unsavedChanges).some((key) => {
|
||||
return settingsEditableConfig[key].requiresPageReload;
|
||||
});
|
||||
await saveAll();
|
||||
if (reloadPage) {
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
notifications.toasts.addDanger({
|
||||
title: i18n.translate('xpack.apm.apmSettings.save.error', {
|
||||
defaultMessage: 'An error occurred while saving the settings',
|
||||
}),
|
||||
text: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{apmSettingsKeys.map((settingKey) => {
|
||||
const editableConfig = settingsEditableConfig[settingKey];
|
||||
return (
|
||||
<LazyField
|
||||
key={settingKey}
|
||||
setting={editableConfig}
|
||||
handleChange={handleFieldChange}
|
||||
enableSaving
|
||||
docLinks={docLinks.links}
|
||||
toasts={notifications.toasts}
|
||||
unsavedChanges={unsavedChanges[settingKey]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<EuiButton
|
||||
fill
|
||||
isLoading={isSaving}
|
||||
disabled={isEmpty(unsavedChanges)}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{i18n.translate('xpack.apm.labs.reload', {
|
||||
defaultMessage: 'Reload to apply changes',
|
||||
})}
|
||||
</EuiButton>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -20,6 +20,7 @@ import { CustomLinkOverview } from '../../app/settings/custom_link';
|
|||
import { Schema } from '../../app/settings/schema';
|
||||
import { AnomalyDetection } from '../../app/settings/anomaly_detection';
|
||||
import { AgentKeys } from '../../app/settings/agent_keys';
|
||||
import { GeneralSettings } from '../../app/settings/general_settings';
|
||||
|
||||
function page({
|
||||
title,
|
||||
|
@ -54,6 +55,14 @@ export const settings = {
|
|||
</Breadcrumb>
|
||||
),
|
||||
children: {
|
||||
'/settings/general-settings': page({
|
||||
title: i18n.translate(
|
||||
'xpack.apm.views.settings.generalSettings.title',
|
||||
{ defaultMessage: 'General settings' }
|
||||
),
|
||||
element: <GeneralSettings />,
|
||||
tab: 'general-settings',
|
||||
}),
|
||||
'/settings/agent-configuration': page({
|
||||
tab: 'agent-configuration',
|
||||
title: i18n.translate(
|
||||
|
@ -133,7 +142,7 @@ export const settings = {
|
|||
tab: 'agent-keys',
|
||||
}),
|
||||
'/settings': {
|
||||
element: <Redirect to="/settings/agent-configuration" />,
|
||||
element: <Redirect to="/settings/general-settings" />,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -8,12 +8,11 @@
|
|||
import { EuiPageHeaderProps } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { History } from 'history';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { ApmMainTemplate } from './apm_main_template';
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { getLegacyApmHref } from '../../shared/links/apm/apm_link';
|
||||
import { useApmRouter } from '../../../hooks/use_apm_router';
|
||||
import { ApmRouter } from '../apm_route_config';
|
||||
|
||||
type Tab = NonNullable<EuiPageHeaderProps['tabs']>[0] & {
|
||||
key:
|
||||
|
@ -22,7 +21,8 @@ type Tab = NonNullable<EuiPageHeaderProps['tabs']>[0] & {
|
|||
| 'anomaly-detection'
|
||||
| 'apm-indices'
|
||||
| 'custom-links'
|
||||
| 'schema';
|
||||
| 'schema'
|
||||
| 'general-settings';
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
|
@ -33,8 +33,8 @@ interface Props {
|
|||
|
||||
export function SettingsTemplate({ children, selectedTab }: Props) {
|
||||
const { core } = useApmPluginContext();
|
||||
const history = useHistory();
|
||||
const tabs = getTabs({ history, core, selectedTab });
|
||||
const router = useApmRouter();
|
||||
const tabs = getTabs({ core, selectedTab, router });
|
||||
|
||||
return (
|
||||
<ApmMainTemplate
|
||||
|
@ -52,51 +52,44 @@ export function SettingsTemplate({ children, selectedTab }: Props) {
|
|||
}
|
||||
|
||||
function getTabs({
|
||||
history,
|
||||
core,
|
||||
selectedTab,
|
||||
router,
|
||||
}: {
|
||||
history: History;
|
||||
core: CoreStart;
|
||||
selectedTab: Tab['key'];
|
||||
router: ApmRouter;
|
||||
}) {
|
||||
const { basePath } = core.http;
|
||||
const canAccessML = !!core.application.capabilities.ml?.canAccessML;
|
||||
const { search } = history.location;
|
||||
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
key: 'general-settings',
|
||||
label: i18n.translate('xpack.apm.settings.generalSettings', {
|
||||
defaultMessage: 'General settings',
|
||||
}),
|
||||
href: router.link('/settings/general-settings'),
|
||||
},
|
||||
{
|
||||
key: 'agent-configuration',
|
||||
label: i18n.translate('xpack.apm.settings.agentConfig', {
|
||||
defaultMessage: 'Agent Configuration',
|
||||
}),
|
||||
href: getLegacyApmHref({
|
||||
basePath,
|
||||
path: `/settings/agent-configuration`,
|
||||
search,
|
||||
}),
|
||||
href: router.link('/settings/agent-configuration'),
|
||||
},
|
||||
{
|
||||
key: 'agent-keys',
|
||||
label: i18n.translate('xpack.apm.settings.agentKeys', {
|
||||
defaultMessage: 'Agent Keys',
|
||||
}),
|
||||
href: getLegacyApmHref({
|
||||
basePath,
|
||||
path: `/settings/agent-keys`,
|
||||
search,
|
||||
}),
|
||||
href: router.link('/settings/agent-keys'),
|
||||
},
|
||||
{
|
||||
key: 'anomaly-detection',
|
||||
label: i18n.translate('xpack.apm.settings.anomalyDetection', {
|
||||
defaultMessage: 'Anomaly detection',
|
||||
}),
|
||||
href: getLegacyApmHref({
|
||||
basePath,
|
||||
path: `/settings/anomaly-detection`,
|
||||
search,
|
||||
}),
|
||||
href: router.link('/settings/anomaly-detection'),
|
||||
hidden: !canAccessML,
|
||||
},
|
||||
{
|
||||
|
@ -104,29 +97,21 @@ function getTabs({
|
|||
label: i18n.translate('xpack.apm.settings.customizeApp', {
|
||||
defaultMessage: 'Custom Links',
|
||||
}),
|
||||
href: getLegacyApmHref({
|
||||
basePath,
|
||||
path: `/settings/custom-links`,
|
||||
search,
|
||||
}),
|
||||
href: router.link('/settings/custom-links'),
|
||||
},
|
||||
{
|
||||
key: 'apm-indices',
|
||||
label: i18n.translate('xpack.apm.settings.indices', {
|
||||
defaultMessage: 'Indices',
|
||||
}),
|
||||
href: getLegacyApmHref({
|
||||
basePath,
|
||||
path: `/settings/apm-indices`,
|
||||
search,
|
||||
}),
|
||||
href: router.link('/settings/apm-indices'),
|
||||
},
|
||||
{
|
||||
key: 'schema',
|
||||
label: i18n.translate('xpack.apm.settings.schema', {
|
||||
defaultMessage: 'Schema',
|
||||
}),
|
||||
href: getLegacyApmHref({ basePath, path: `/settings/schema`, search }),
|
||||
href: router.link('/settings/schema'),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { EuiHeaderLink, EuiHeaderLinks } from '@elastic/eui';
|
||||
import { apmLabsButton } from '@kbn/observability-plugin/common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { getAlertingCapabilities } from '../../alerting/get_alerting_capabilities';
|
||||
|
@ -15,6 +16,7 @@ import { AlertingPopoverAndFlyout } from './alerting_popover_flyout';
|
|||
import { AnomalyDetectionSetupLink } from './anomaly_detection_setup_link';
|
||||
import { useServiceName } from '../../../hooks/use_service_name';
|
||||
import { InspectorHeaderLink } from './inspector_header_link';
|
||||
import { Labs } from './labs';
|
||||
|
||||
export function ApmHeaderActionMenu() {
|
||||
const { core, plugins } = useApmPluginContext();
|
||||
|
@ -40,8 +42,14 @@ export function ApmHeaderActionMenu() {
|
|||
return basePath.prepend(path);
|
||||
}
|
||||
|
||||
const isLabsButtonEnabled = core.uiSettings.get<boolean>(
|
||||
apmLabsButton,
|
||||
false
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiHeaderLinks gutterSize="xs">
|
||||
{isLabsButtonEnabled && <Labs />}
|
||||
<EuiHeaderLink
|
||||
color="text"
|
||||
href={apmHref('/storage-explorer')}
|
||||
|
|
|
@ -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 { EuiButtonEmpty } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState } from 'react';
|
||||
import { LabsFlyout } from './labs_flyout';
|
||||
|
||||
export function Labs() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
function toggleFlyoutVisibility() {
|
||||
setIsOpen((state) => !state);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiButtonEmpty color="text" onClick={toggleFlyoutVisibility}>
|
||||
{i18n.translate('xpack.apm.labs', { defaultMessage: 'Labs' })}
|
||||
</EuiButtonEmpty>
|
||||
{isOpen && <LabsFlyout onClose={toggleFlyoutVisibility} />}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiHorizontalRule,
|
||||
EuiIcon,
|
||||
EuiLoadingContent,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { LazyField } from '@kbn/advanced-settings-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useApmEditableSettings } from '../../../../hooks/use_apm_editable_settings';
|
||||
import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function LabsFlyout({ onClose }: Props) {
|
||||
const { docLinks, notifications } = useApmPluginContext().core;
|
||||
|
||||
const { data, status } = useFetcher(
|
||||
(callApmApi) => callApmApi('GET /internal/apm/settings/labs'),
|
||||
[]
|
||||
);
|
||||
const labsItems = data?.labsItems || [];
|
||||
|
||||
const {
|
||||
handleFieldChange,
|
||||
settingsEditableConfig,
|
||||
unsavedChanges,
|
||||
saveAll,
|
||||
isSaving,
|
||||
cleanUnsavedChanges,
|
||||
} = useApmEditableSettings(labsItems);
|
||||
|
||||
async function handleSave() {
|
||||
try {
|
||||
const reloadPage = Object.keys(unsavedChanges).some((key) => {
|
||||
return settingsEditableConfig[key].requiresPageReload;
|
||||
});
|
||||
|
||||
await saveAll();
|
||||
|
||||
if (reloadPage) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
} catch (e) {
|
||||
const error = e as Error;
|
||||
notifications.toasts.addDanger({
|
||||
title: i18n.translate('xpack.apm.apmSettings.save.error', {
|
||||
defaultMessage: 'An error occurred while saving the settings',
|
||||
}),
|
||||
text: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handelCancel() {
|
||||
cleanUnsavedChanges();
|
||||
onClose();
|
||||
}
|
||||
|
||||
const isLoading =
|
||||
status === FETCH_STATUS.NOT_INITIATED || status === FETCH_STATUS.LOADING;
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onClose}>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="beaker" size="xl" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
{i18n.translate('xpack.apm.labs', {
|
||||
defaultMessage: 'Labs',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
{isLoading ? (
|
||||
<EuiLoadingContent lines={3} />
|
||||
) : (
|
||||
<>
|
||||
<EuiFlyoutBody>
|
||||
{labsItems.map((settingKey, i) => {
|
||||
const editableConfig = settingsEditableConfig[settingKey];
|
||||
return (
|
||||
<>
|
||||
<LazyField
|
||||
key={settingKey}
|
||||
setting={editableConfig}
|
||||
handleChange={handleFieldChange}
|
||||
enableSaving
|
||||
docLinks={docLinks.links}
|
||||
toasts={notifications.toasts}
|
||||
unsavedChanges={unsavedChanges[settingKey]}
|
||||
/>
|
||||
<EuiHorizontalRule />
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={handelCancel}>
|
||||
{i18n.translate('xpack.apm.labs.cancel', {
|
||||
defaultMessage: 'Cancel',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill isLoading={isSaving} onClick={handleSave}>
|
||||
{i18n.translate('xpack.apm.labs.reload', {
|
||||
defaultMessage: 'Reload to apply changes',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</>
|
||||
)}
|
||||
</EuiFlyout>
|
||||
);
|
||||
}
|
104
x-pack/plugins/apm/public/hooks/use_apm_editable_settings.tsx
Normal file
104
x-pack/plugins/apm/public/hooks/use_apm_editable_settings.tsx
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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 { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { FieldState } from '@kbn/advanced-settings-plugin/public';
|
||||
import { toEditableConfig } from '@kbn/advanced-settings-plugin/public';
|
||||
import { IUiSettingsClient } from '@kbn/core/public';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
function getEditableConfig({
|
||||
settingsKeys,
|
||||
uiSettings,
|
||||
}: {
|
||||
settingsKeys: string[];
|
||||
uiSettings?: IUiSettingsClient;
|
||||
}) {
|
||||
if (!uiSettings) {
|
||||
return {};
|
||||
}
|
||||
const uiSettingsDefinition = uiSettings.getAll();
|
||||
const config: Record<string, ReturnType<typeof toEditableConfig>> = {};
|
||||
|
||||
settingsKeys.forEach((key) => {
|
||||
const settingDef = uiSettingsDefinition?.[key];
|
||||
if (settingDef) {
|
||||
const editableConfig = toEditableConfig({
|
||||
def: settingDef,
|
||||
name: key,
|
||||
value: settingDef.userValue,
|
||||
isCustom: uiSettings.isCustom(key),
|
||||
isOverridden: uiSettings.isOverridden(key),
|
||||
});
|
||||
config[key] = editableConfig;
|
||||
}
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
export function useApmEditableSettings(settingsKeys: string[]) {
|
||||
const { services } = useKibana();
|
||||
const { uiSettings } = services;
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [forceReloadSettings, setForceReloadSettings] = useState(0);
|
||||
const [unsavedChanges, setUnsavedChanges] = useState<
|
||||
Record<string, FieldState>
|
||||
>({});
|
||||
|
||||
const settingsEditableConfig = useMemo(
|
||||
() => {
|
||||
return getEditableConfig({ settingsKeys, uiSettings });
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[uiSettings, settingsKeys, forceReloadSettings]
|
||||
);
|
||||
|
||||
function handleFieldChange(key: string, fieldState: FieldState) {
|
||||
setUnsavedChanges((state) => {
|
||||
const newState = { ...state };
|
||||
const { value, defVal } = settingsEditableConfig[key];
|
||||
const currentValue = value === undefined ? defVal : value;
|
||||
if (currentValue === fieldState.value) {
|
||||
// Delete property from unsaved object if user changes it to the value that was already saved
|
||||
delete newState[key];
|
||||
} else {
|
||||
newState[key] = fieldState;
|
||||
}
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
|
||||
function cleanUnsavedChanges() {
|
||||
setUnsavedChanges({});
|
||||
}
|
||||
|
||||
async function saveAll() {
|
||||
if (uiSettings && !isEmpty(unsavedChanges)) {
|
||||
try {
|
||||
setIsSaving(true);
|
||||
const arr = Object.entries(unsavedChanges).map(([key, fieldState]) =>
|
||||
uiSettings.set(key, fieldState.value)
|
||||
);
|
||||
|
||||
await Promise.all(arr);
|
||||
setForceReloadSettings((state) => ++state);
|
||||
cleanUnsavedChanges();
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
settingsEditableConfig,
|
||||
unsavedChanges,
|
||||
handleFieldChange,
|
||||
saveAll,
|
||||
isSaving,
|
||||
cleanUnsavedChanges,
|
||||
};
|
||||
}
|
|
@ -41,6 +41,7 @@ import { timeRangeMetadataRoute } from '../time_range_metadata/route';
|
|||
import { traceRouteRepository } from '../traces/route';
|
||||
import { transactionRouteRepository } from '../transactions/route';
|
||||
import { storageExplorerRouteRepository } from '../storage_explorer/route';
|
||||
import { labsRouteRepository } from '../settings/labs/route';
|
||||
|
||||
function getTypedGlobalApmServerRouteRepository() {
|
||||
const repository = {
|
||||
|
@ -75,6 +76,7 @@ function getTypedGlobalApmServerRouteRepository() {
|
|||
...infrastructureRouteRepository,
|
||||
...debugTelemetryRoute,
|
||||
...timeRangeMetadataRoute,
|
||||
...labsRouteRepository,
|
||||
};
|
||||
|
||||
return repository;
|
||||
|
|
22
x-pack/plugins/apm/server/routes/settings/labs/route.ts
Normal file
22
x-pack/plugins/apm/server/routes/settings/labs/route.ts
Normal file
|
@ -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 { uiSettings } from '@kbn/observability-plugin/server';
|
||||
import { createApmServerRoute } from '../../apm_routes/create_apm_server_route';
|
||||
|
||||
const getLabsRoute = createApmServerRoute({
|
||||
endpoint: 'GET /internal/apm/settings/labs',
|
||||
options: { tags: ['access:apm'] },
|
||||
handler: async (): Promise<{ labsItems: string[] }> => {
|
||||
const labsItems = Object.entries(uiSettings)
|
||||
.filter(([key, value]): boolean | undefined => value.showInLabs)
|
||||
.map(([key]): string => key);
|
||||
return { labsItems };
|
||||
},
|
||||
});
|
||||
|
||||
export const labsRouteRepository = getLabsRoute;
|
|
@ -12,15 +12,18 @@ export { formatDurationFromTimeUnitChar } from './utils/formatters';
|
|||
export { ProcessorEvent } from './processor_event';
|
||||
|
||||
export {
|
||||
enableNewSyntheticsView,
|
||||
enableInspectEsQueries,
|
||||
maxSuggestions,
|
||||
enableComparisonByDefault,
|
||||
defaultApmServiceEnvironment,
|
||||
apmServiceInventoryOptimizedSorting,
|
||||
apmProgressiveLoading,
|
||||
enableServiceGroups,
|
||||
apmServiceInventoryOptimizedSorting,
|
||||
apmServiceGroupMaxNumberOfServices,
|
||||
apmTraceExplorerTab,
|
||||
apmOperationsTab,
|
||||
apmLabsButton,
|
||||
} from './ui_settings_keys';
|
||||
|
||||
export {
|
||||
|
|
|
@ -18,3 +18,4 @@ export const apmServiceGroupMaxNumberOfServices =
|
|||
'observability:apmServiceGroupMaxNumberOfServices';
|
||||
export const apmTraceExplorerTab = 'observability:apmTraceExplorerTab';
|
||||
export const apmOperationsTab = 'observability:apmOperationsTab';
|
||||
export const apmLabsButton = 'observability:apmLabsButton';
|
||||
|
|
|
@ -51,3 +51,5 @@ export const plugin = (initContext: PluginInitializerContext) =>
|
|||
|
||||
export type { Mappings, ObservabilityPluginSetup, ScopedAnnotationsClient };
|
||||
export { createOrUpdateIndex, unwrapEsResponse, WrappedElasticsearchClientError };
|
||||
|
||||
export { uiSettings } from './ui_settings';
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
apmServiceGroupMaxNumberOfServices,
|
||||
apmTraceExplorerTab,
|
||||
apmOperationsTab,
|
||||
apmLabsButton,
|
||||
} from '../common/ui_settings_keys';
|
||||
|
||||
const technicalPreviewLabel = i18n.translate(
|
||||
|
@ -30,10 +31,12 @@ const technicalPreviewLabel = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
type UiSettings = UiSettingsParams<boolean | number | string> & { showInLabs?: boolean };
|
||||
|
||||
/**
|
||||
* uiSettings definitions for Observability.
|
||||
*/
|
||||
export const uiSettings: Record<string, UiSettingsParams<boolean | number | string>> = {
|
||||
export const uiSettings: Record<string, UiSettings> = {
|
||||
[enableNewSyntheticsView]: {
|
||||
category: [observabilityFeatureId],
|
||||
name: i18n.translate('xpack.observability.enableNewSyntheticsViewExperimentName', {
|
||||
|
@ -60,6 +63,7 @@ export const uiSettings: Record<string, UiSettingsParams<boolean | number | stri
|
|||
defaultMessage: 'Inspect Elasticsearch queries in API responses.',
|
||||
}),
|
||||
schema: schema.boolean(),
|
||||
requiresPageReload: true,
|
||||
},
|
||||
[maxSuggestions]: {
|
||||
category: [observabilityFeatureId],
|
||||
|
@ -160,6 +164,7 @@ export const uiSettings: Record<string, UiSettingsParams<boolean | number | stri
|
|||
}),
|
||||
schema: schema.boolean(),
|
||||
requiresPageReload: true,
|
||||
showInLabs: true,
|
||||
},
|
||||
[apmServiceInventoryOptimizedSorting]: {
|
||||
category: [observabilityFeatureId],
|
||||
|
@ -178,6 +183,7 @@ export const uiSettings: Record<string, UiSettingsParams<boolean | number | stri
|
|||
value: false,
|
||||
requiresPageReload: false,
|
||||
type: 'boolean',
|
||||
showInLabs: true,
|
||||
},
|
||||
[apmServiceGroupMaxNumberOfServices]: {
|
||||
category: [observabilityFeatureId],
|
||||
|
@ -204,6 +210,7 @@ export const uiSettings: Record<string, UiSettingsParams<boolean | number | stri
|
|||
value: false,
|
||||
requiresPageReload: true,
|
||||
type: 'boolean',
|
||||
showInLabs: true,
|
||||
},
|
||||
[apmOperationsTab]: {
|
||||
category: [observabilityFeatureId],
|
||||
|
@ -219,5 +226,20 @@ export const uiSettings: Record<string, UiSettingsParams<boolean | number | stri
|
|||
value: false,
|
||||
requiresPageReload: true,
|
||||
type: 'boolean',
|
||||
showInLabs: true,
|
||||
},
|
||||
[apmLabsButton]: {
|
||||
category: [observabilityFeatureId],
|
||||
name: i18n.translate('xpack.observability.apmLabs', {
|
||||
defaultMessage: 'Enable labs button in APM',
|
||||
}),
|
||||
description: i18n.translate('xpack.observability.apmLabsDescription', {
|
||||
defaultMessage:
|
||||
'This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in APM.',
|
||||
}),
|
||||
schema: schema.boolean(),
|
||||
value: false,
|
||||
requiresPageReload: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue