mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ObsUx] Add feedback form to apm (#173758)
Closes #172506 ## Summary This PR adds a button to all APM pages navigating to a feedback form. The feedback button component exists in infra so in this PR the component is changed to meet the new requirements and moved to `observability_shared`. Now the feedback button component supports also prefilling the sanitized path (`entry.1876422621`). The requirements picked for the sanitized path are described in the issue - it should be for example `/app/apm/{page_name}` if the user is on the page with the exact path matching `/app/apm/{page_name}` and `/app/apm/{page_name}*` for all subpages (for example `/app/apm/{page_name}/something/else`) - different test scenarios can be found in the unit test: [get_path_for_feedback.test.ts](https://github.com/elastic/kibana/compare/main...jennypavlova:kibana:172506-obsux-add-feedback-form-to-apm?expand=1#diff-6396807e61353509c44fa38488dfb741549e60f25126024b92596f6b7ac933b8) ## Testing - Go to APM - All APM pages should have a yellow button with the text: `Tell us what you think!` - Hover on the button to see the prefilled properties and check if every property is prefilled <img width="1920" alt="image" src="b57adf04
-4e7b-42bb-827f-db554dc4a842"> - Click on the form and check if the form is prefilled - The questions that should be prefilled in APM - Where is your Elastic cluster deployed? (same as infra) - By default, the pre-selected option will be on-prem - To test the case with the cloud option preselected add `xpack.cloud.id: 'elastic_kibana_dev'` to your `kibana.dev.yaml` - To test the serverless option run Kibana in serverless mode (observability config) - What version of Elastic are you using? (same as infra) - Where in the UI are you? - After checking the APM go to the infra pages and check the feedback button/form52cc886f
-95a9-4099-b047-93fc64036572
This commit is contained in:
parent
d458b5382f
commit
35d037b2d5
23 changed files with 356 additions and 84 deletions
|
@ -20,6 +20,7 @@ import { createCallApmApi } from '../services/rest/create_call_apm_api';
|
|||
import { setHelpExtension } from '../set_help_extension';
|
||||
import { setReadonlyBadge } from '../update_badge';
|
||||
import { ApmAppRoot } from '../components/routing/app_root';
|
||||
import type { KibanaEnvContext } from '../context/kibana_environment_context/kibana_environment_context';
|
||||
|
||||
/**
|
||||
* This module is rendered asynchronously in the Kibana platform.
|
||||
|
@ -32,6 +33,7 @@ export const renderApp = ({
|
|||
pluginsStart,
|
||||
observabilityRuleTypeRegistry,
|
||||
apmServices,
|
||||
kibanaEnvironment,
|
||||
}: {
|
||||
coreStart: CoreStart;
|
||||
pluginsSetup: ApmPluginSetupDeps;
|
||||
|
@ -40,6 +42,7 @@ export const renderApp = ({
|
|||
pluginsStart: ApmPluginStartDeps;
|
||||
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
|
||||
apmServices: ApmServices;
|
||||
kibanaEnvironment: KibanaEnvContext;
|
||||
}) => {
|
||||
const { element, theme$ } = appMountParameters;
|
||||
const apmPluginContextValue = {
|
||||
|
@ -58,6 +61,7 @@ export const renderApp = ({
|
|||
uiActions: pluginsStart.uiActions,
|
||||
observabilityAIAssistant: pluginsStart.observabilityAIAssistant,
|
||||
share: pluginsSetup.share,
|
||||
kibanaEnvironment,
|
||||
};
|
||||
|
||||
// render APM feedback link in global help menu
|
||||
|
|
|
@ -21,6 +21,7 @@ import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config';
|
|||
import { euiDarkVars, euiLightVars } from '@kbn/ui-theme';
|
||||
import React from 'react';
|
||||
import { DefaultTheme, ThemeProvider } from 'styled-components';
|
||||
import { useKibanaEnvironmentContextProvider } from '../../../context/kibana_environment_context/use_kibana_environment_context';
|
||||
import { AnomalyDetectionJobsContextProvider } from '../../../context/anomaly_detection_jobs/anomaly_detection_jobs_context';
|
||||
import {
|
||||
ApmPluginContext,
|
||||
|
@ -54,7 +55,9 @@ export function ApmAppRoot({
|
|||
pluginsStart: ApmPluginStartDeps;
|
||||
apmServices: ApmServices;
|
||||
}) {
|
||||
const { appMountParameters, core } = apmPluginContextValue;
|
||||
const { appMountParameters, kibanaEnvironment, core } = apmPluginContextValue;
|
||||
const KibanaEnvironmentContextProvider =
|
||||
useKibanaEnvironmentContextProvider(kibanaEnvironment);
|
||||
const { history } = appMountParameters;
|
||||
const i18nCore = core.i18n;
|
||||
|
||||
|
@ -73,46 +76,50 @@ export function ApmAppRoot({
|
|||
<KibanaContextProvider
|
||||
services={{ ...core, ...pluginsStart, storage, ...apmServices }}
|
||||
>
|
||||
<i18nCore.Context>
|
||||
<TimeRangeIdContextProvider>
|
||||
<RouterProvider history={history} router={apmRouter as any}>
|
||||
<ApmErrorBoundary>
|
||||
<RedirectDependenciesToDependenciesInventory>
|
||||
<RedirectWithDefaultEnvironment>
|
||||
<RedirectWithDefaultDateRange>
|
||||
<RedirectWithOffset>
|
||||
<TrackPageview>
|
||||
<UpdateExecutionContextOnRouteChange>
|
||||
<BreadcrumbsContextProvider>
|
||||
<UrlParamsProvider>
|
||||
<LicenseProvider>
|
||||
<AnomalyDetectionJobsContextProvider>
|
||||
<InspectorContextProvider>
|
||||
<ApmThemeProvider>
|
||||
<MountApmHeaderActionMenu />
|
||||
<KibanaEnvironmentContextProvider
|
||||
kibanaEnvironment={kibanaEnvironment}
|
||||
>
|
||||
<i18nCore.Context>
|
||||
<TimeRangeIdContextProvider>
|
||||
<RouterProvider history={history} router={apmRouter as any}>
|
||||
<ApmErrorBoundary>
|
||||
<RedirectDependenciesToDependenciesInventory>
|
||||
<RedirectWithDefaultEnvironment>
|
||||
<RedirectWithDefaultDateRange>
|
||||
<RedirectWithOffset>
|
||||
<TrackPageview>
|
||||
<UpdateExecutionContextOnRouteChange>
|
||||
<BreadcrumbsContextProvider>
|
||||
<UrlParamsProvider>
|
||||
<LicenseProvider>
|
||||
<AnomalyDetectionJobsContextProvider>
|
||||
<InspectorContextProvider>
|
||||
<ApmThemeProvider>
|
||||
<MountApmHeaderActionMenu />
|
||||
|
||||
<Route
|
||||
component={
|
||||
ScrollToTopOnPathChange
|
||||
}
|
||||
/>
|
||||
<RouteRenderer />
|
||||
</ApmThemeProvider>
|
||||
</InspectorContextProvider>
|
||||
</AnomalyDetectionJobsContextProvider>
|
||||
</LicenseProvider>
|
||||
</UrlParamsProvider>
|
||||
</BreadcrumbsContextProvider>
|
||||
</UpdateExecutionContextOnRouteChange>
|
||||
</TrackPageview>
|
||||
</RedirectWithOffset>
|
||||
</RedirectWithDefaultDateRange>
|
||||
</RedirectWithDefaultEnvironment>
|
||||
</RedirectDependenciesToDependenciesInventory>
|
||||
</ApmErrorBoundary>
|
||||
</RouterProvider>
|
||||
</TimeRangeIdContextProvider>
|
||||
</i18nCore.Context>
|
||||
<Route
|
||||
component={
|
||||
ScrollToTopOnPathChange
|
||||
}
|
||||
/>
|
||||
<RouteRenderer />
|
||||
</ApmThemeProvider>
|
||||
</InspectorContextProvider>
|
||||
</AnomalyDetectionJobsContextProvider>
|
||||
</LicenseProvider>
|
||||
</UrlParamsProvider>
|
||||
</BreadcrumbsContextProvider>
|
||||
</UpdateExecutionContextOnRouteChange>
|
||||
</TrackPageview>
|
||||
</RedirectWithOffset>
|
||||
</RedirectWithDefaultDateRange>
|
||||
</RedirectWithDefaultEnvironment>
|
||||
</RedirectDependenciesToDependenciesInventory>
|
||||
</ApmErrorBoundary>
|
||||
</RouterProvider>
|
||||
</TimeRangeIdContextProvider>
|
||||
</i18nCore.Context>
|
||||
</KibanaEnvironmentContextProvider>
|
||||
</KibanaContextProvider>
|
||||
</ApmPluginContext.Provider>
|
||||
</RedirectAppLinks>
|
||||
|
|
|
@ -9,8 +9,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiPageHeaderProps } from '@elastic/eui';
|
|||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { ObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public';
|
||||
import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template';
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public';
|
||||
import { KibanaEnvironmentContext } from '../../../context/kibana_environment_context/kibana_environment_context';
|
||||
import { getPathForFeedback } from '../../../utils/get_path_for_feedback';
|
||||
import { EnvironmentsContextProvider } from '../../../context/environments_context/environments_context';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
|
||||
import { ApmPluginStartDeps } from '../../../plugin';
|
||||
|
@ -22,6 +25,7 @@ import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_
|
|||
|
||||
// Paths that must skip the no data screen
|
||||
const bypassNoDataScreenPaths = ['/settings', '/diagnostics'];
|
||||
const APM_FEEDBACK_LINK = 'https://ela.st/services-feedback';
|
||||
|
||||
/*
|
||||
* This template contains:
|
||||
|
@ -56,7 +60,9 @@ export function ApmMainTemplate({
|
|||
const location = useLocation();
|
||||
|
||||
const { services } = useKibana<ApmPluginStartDeps>();
|
||||
const kibanaEnvironment = useContext(KibanaEnvironmentContext);
|
||||
const { http, docLinks, observabilityShared, application } = services;
|
||||
const { kibanaVersion, isCloudEnv, isServerlessEnv } = kibanaEnvironment;
|
||||
const basePath = http?.basePath.get();
|
||||
const { config } = useApmPluginContext();
|
||||
|
||||
|
@ -66,7 +72,7 @@ export function ApmMainTemplate({
|
|||
return callApmApi('GET /internal/apm/has_data');
|
||||
}, []);
|
||||
|
||||
// create static data view on inital load
|
||||
// create static data view on initial load
|
||||
useFetcher(
|
||||
(callApmApi) => {
|
||||
const canCreateDataView =
|
||||
|
@ -111,11 +117,26 @@ export function ApmMainTemplate({
|
|||
...(showServiceGroupSaveButton ? [<ServiceGroupSaveButton />] : []),
|
||||
];
|
||||
|
||||
const sanitizedPath = getPathForFeedback(window.location.pathname);
|
||||
const pageHeaderTitle = (
|
||||
<EuiFlexGroup justifyContent="spaceBetween" wrap={true}>
|
||||
{pageHeader?.pageTitle ?? pageTitle}
|
||||
<EuiFlexItem grow={false}>
|
||||
{environmentFilter && <ApmEnvironmentFilter />}
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<FeatureFeedbackButton
|
||||
data-test-subj="infraApmFeedbackLink"
|
||||
formUrl={APM_FEEDBACK_LINK}
|
||||
kibanaVersion={kibanaVersion}
|
||||
isCloudEnv={isCloudEnv}
|
||||
isServerlessEnv={isServerlessEnv}
|
||||
sanitizedPath={sanitizedPath}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{environmentFilter && <ApmEnvironmentFilter />}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -19,6 +19,7 @@ import type { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-
|
|||
import { SharePluginSetup } from '@kbn/share-plugin/public';
|
||||
import type { ApmPluginSetupDeps } from '../../plugin';
|
||||
import type { ConfigSchema } from '../..';
|
||||
import type { KibanaEnvContext } from '../kibana_environment_context/kibana_environment_context';
|
||||
|
||||
export interface ApmPluginContextValue {
|
||||
appMountParameters: AppMountParameters;
|
||||
|
@ -34,6 +35,7 @@ export interface ApmPluginContextValue {
|
|||
uiActions: UiActionsStart;
|
||||
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
|
||||
share: SharePluginSetup;
|
||||
kibanaEnvironment: KibanaEnvContext;
|
||||
}
|
||||
|
||||
export const ApmPluginContext = createContext({} as ApmPluginContextValue);
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { createContext } from 'react';
|
||||
|
||||
export interface KibanaEnvContext {
|
||||
kibanaVersion?: string;
|
||||
isCloudEnv?: boolean;
|
||||
isServerlessEnv?: boolean;
|
||||
}
|
||||
|
||||
export const KibanaEnvironmentContext = createContext<KibanaEnvContext>({});
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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, { useMemo, createElement } from 'react';
|
||||
import {
|
||||
KibanaEnvironmentContext,
|
||||
type KibanaEnvContext,
|
||||
} from './kibana_environment_context';
|
||||
|
||||
export const useKibanaEnvironmentContextProvider = ({
|
||||
kibanaVersion,
|
||||
isCloudEnv,
|
||||
isServerlessEnv,
|
||||
}: KibanaEnvContext) => {
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
kibanaVersion,
|
||||
isCloudEnv,
|
||||
isServerlessEnv,
|
||||
}),
|
||||
[kibanaVersion, isCloudEnv, isServerlessEnv]
|
||||
);
|
||||
|
||||
const Provider: React.FC<{ kibanaEnvironment?: KibanaEnvContext }> = ({
|
||||
kibanaEnvironment = {},
|
||||
children,
|
||||
}) => {
|
||||
const newProvider = createElement(KibanaEnvironmentContext.Provider, {
|
||||
value: { ...kibanaEnvironment, ...value },
|
||||
children,
|
||||
});
|
||||
|
||||
return newProvider;
|
||||
};
|
||||
|
||||
return Provider;
|
||||
};
|
|
@ -73,6 +73,7 @@ import { DashboardStart } from '@kbn/dashboard-plugin/public';
|
|||
import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
|
||||
import { from } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import type { CloudSetup } from '@kbn/cloud-plugin/public';
|
||||
import type { ConfigSchema } from '.';
|
||||
import { registerApmRuleTypes } from './components/alerting/rule_types/register_apm_rule_types';
|
||||
import {
|
||||
|
@ -106,6 +107,7 @@ export interface ApmPluginSetupDeps {
|
|||
share: SharePluginSetup;
|
||||
uiActions: UiActionsSetup;
|
||||
profiling?: ProfilingPluginSetup;
|
||||
cloud?: CloudSetup;
|
||||
}
|
||||
|
||||
export interface ApmServices {
|
||||
|
@ -190,11 +192,16 @@ const apmTutorialTitle = i18n.translate(
|
|||
|
||||
export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
||||
private telemetry: TelemetryService;
|
||||
private kibanaVersion: string;
|
||||
private isServerlessEnv: boolean;
|
||||
constructor(
|
||||
private readonly initializerContext: PluginInitializerContext<ConfigSchema>
|
||||
) {
|
||||
this.initializerContext = initializerContext;
|
||||
this.telemetry = new TelemetryService();
|
||||
this.kibanaVersion = initializerContext.env.packageInfo.version;
|
||||
this.isServerlessEnv =
|
||||
initializerContext.env.packageInfo.buildFlavor === 'serverless';
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup, plugins: ApmPluginSetupDeps) {
|
||||
|
@ -396,17 +403,25 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
|||
{ id: 'tutorial', title: apmTutorialTitle, path: '/tutorial' },
|
||||
],
|
||||
|
||||
async mount(appMountParameters: AppMountParameters<unknown>) {
|
||||
mount: async (appMountParameters: AppMountParameters<unknown>) => {
|
||||
// Load application bundle and Get start services
|
||||
const [{ renderApp }, [coreStart, pluginsStart]] = await Promise.all([
|
||||
import('./application'),
|
||||
core.getStartServices(),
|
||||
]);
|
||||
const isCloudEnv = !!pluginSetupDeps.cloud?.isCloudEnabled;
|
||||
const isServerlessEnv =
|
||||
pluginSetupDeps.cloud?.isServerlessEnabled || this.isServerlessEnv;
|
||||
return renderApp({
|
||||
coreStart,
|
||||
pluginsSetup: pluginSetupDeps,
|
||||
pluginsSetup: pluginSetupDeps as ApmPluginSetupDeps,
|
||||
appMountParameters,
|
||||
config,
|
||||
kibanaEnvironment: {
|
||||
isCloudEnv,
|
||||
isServerlessEnv,
|
||||
kibanaVersion: this.kibanaVersion,
|
||||
},
|
||||
pluginsStart: pluginsStart as ApmPluginStartDeps,
|
||||
observabilityRuleTypeRegistry,
|
||||
apmServices: {
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { getPathForFeedback } from './get_path_for_feedback';
|
||||
|
||||
describe('getPathForFeedback ', () => {
|
||||
const testData = [
|
||||
{ value: `/ftw/app/apm/traces`, result: '/app/apm/traces' },
|
||||
{ value: `/app/apm/traces`, result: '/app/apm/traces' },
|
||||
{
|
||||
value: `/ftw/app/apm/traces/frontend/transactions/view`,
|
||||
result: '/app/apm/traces*',
|
||||
},
|
||||
{ value: `/app/apm/services`, result: '/app/apm/services' },
|
||||
{
|
||||
value: `/longer/path/before/app/apm/services`,
|
||||
result: '/app/apm/services',
|
||||
},
|
||||
{
|
||||
value: `/ftw/app/apm/services/long/path/after/services`,
|
||||
result: '/app/apm/services*',
|
||||
},
|
||||
{
|
||||
value: `/ftw/app/apm/dependencies/frontend/transactions/view`,
|
||||
result: '/app/apm/dependencies*',
|
||||
},
|
||||
{ value: `/app/apm/dependencies`, result: '/app/apm/dependencies' },
|
||||
{
|
||||
value: `/ftw/app/apm/dependencies/frontend/transactions/view`,
|
||||
result: '/app/apm/dependencies*',
|
||||
},
|
||||
{
|
||||
value: `/ftw/app/apm/settings/frontend/transactions/view`,
|
||||
result: '/app/apm/settings*',
|
||||
},
|
||||
{
|
||||
value: `/app/apm/some-page/frontend/transactions/view`,
|
||||
result: '/app/apm/some-page*',
|
||||
},
|
||||
{
|
||||
value: `/app/apm/settings`,
|
||||
result: '/app/apm/settings',
|
||||
},
|
||||
];
|
||||
|
||||
it.each(testData)(
|
||||
'Returns correct path for the feedback form $value -> $result',
|
||||
({ value, result }) => {
|
||||
expect(getPathForFeedback(value)).toBe(result);
|
||||
}
|
||||
);
|
||||
});
|
26
x-pack/plugins/apm/public/utils/get_path_for_feedback.ts
Normal file
26
x-pack/plugins/apm/public/utils/get_path_for_feedback.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const APP_BASE_PATH = '/app';
|
||||
|
||||
export const shortenPath = (path: string, pathStart: string) => {
|
||||
if (path.startsWith(pathStart)) {
|
||||
return path;
|
||||
}
|
||||
const indexOfPathStart = path.indexOf(pathStart);
|
||||
return path.substring(indexOfPathStart);
|
||||
};
|
||||
|
||||
export const getPathForFeedback = (path: string) => {
|
||||
const pathStartingFromApp = shortenPath(path, APP_BASE_PATH);
|
||||
const pathParts = pathStartingFromApp.split('/');
|
||||
const constructPath = `/${pathParts.slice(1, 4).join('/')}`;
|
||||
if (pathStartingFromApp === constructPath) {
|
||||
return pathStartingFromApp;
|
||||
}
|
||||
return `${constructPath}*`;
|
||||
};
|
|
@ -13,7 +13,11 @@ import type { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-
|
|||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { NavigationWarningPromptProvider } from '@kbn/observability-shared-plugin/public';
|
||||
import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { useKibanaContextForPluginProvider } from '../hooks/use_kibana';
|
||||
import {
|
||||
type KibanaEnvContext,
|
||||
useKibanaContextForPluginProvider,
|
||||
useKibanaEnvironmentContextProvider,
|
||||
} from '../hooks/use_kibana';
|
||||
import { InfraClientStartDeps, InfraClientStartExports } from '../types';
|
||||
import { HeaderActionMenuProvider } from '../utils/header_action_menu_provider';
|
||||
import { TriggersActionsProvider } from '../utils/triggers_actions_context';
|
||||
|
@ -55,6 +59,7 @@ export interface CoreProvidersProps {
|
|||
pluginStart: InfraClientStartExports;
|
||||
plugins: InfraClientStartDeps;
|
||||
theme$: AppMountParameters['theme$'];
|
||||
kibanaEnvironment?: KibanaEnvContext;
|
||||
}
|
||||
|
||||
export const CoreProviders: React.FC<CoreProvidersProps> = ({
|
||||
|
@ -63,6 +68,7 @@ export const CoreProviders: React.FC<CoreProvidersProps> = ({
|
|||
pluginStart,
|
||||
plugins,
|
||||
theme$,
|
||||
kibanaEnvironment,
|
||||
}) => {
|
||||
const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider(
|
||||
core,
|
||||
|
@ -70,11 +76,15 @@ export const CoreProviders: React.FC<CoreProvidersProps> = ({
|
|||
pluginStart
|
||||
);
|
||||
|
||||
const KibanaEnvContextForPluginProvider = useKibanaEnvironmentContextProvider(kibanaEnvironment);
|
||||
|
||||
return (
|
||||
<KibanaContextProviderForPlugin services={{ ...core, ...plugins, ...pluginStart }}>
|
||||
<core.i18n.Context>
|
||||
<KibanaThemeProvider theme$={theme$}>{children}</KibanaThemeProvider>
|
||||
</core.i18n.Context>
|
||||
<KibanaEnvContextForPluginProvider kibanaEnv={kibanaEnvironment}>
|
||||
<core.i18n.Context>
|
||||
<KibanaThemeProvider theme$={theme$}>{children}</KibanaThemeProvider>
|
||||
</core.i18n.Context>
|
||||
</KibanaEnvContextForPluginProvider>
|
||||
</KibanaContextProviderForPlugin>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@ import { CommonInfraProviders, CoreProviders } from './common_providers';
|
|||
import { prepareMountElement } from './common_styles';
|
||||
import { SourceProvider } from '../containers/metrics_source';
|
||||
import { PluginConfigProvider } from '../containers/plugin_config_context';
|
||||
import type { KibanaEnvContext } from '../hooks/use_kibana';
|
||||
|
||||
export const METRICS_APP_DATA_TEST_SUBJ = 'infraMetricsPage';
|
||||
|
||||
|
@ -30,7 +31,8 @@ export const renderApp = (
|
|||
plugins: InfraClientStartDeps,
|
||||
pluginStart: InfraClientStartExports,
|
||||
pluginConfig: InfraPublicConfig,
|
||||
{ element, history, setHeaderActionMenu, theme$ }: AppMountParameters
|
||||
{ element, history, setHeaderActionMenu, theme$ }: AppMountParameters,
|
||||
kibanaEnvironment: KibanaEnvContext
|
||||
) => {
|
||||
const storage = new Storage(window.localStorage);
|
||||
|
||||
|
@ -46,6 +48,7 @@ export const renderApp = (
|
|||
setHeaderActionMenu={setHeaderActionMenu}
|
||||
storage={storage}
|
||||
theme$={theme$}
|
||||
kibanaEnvironment={kibanaEnvironment}
|
||||
/>,
|
||||
element
|
||||
);
|
||||
|
@ -66,6 +69,7 @@ const MetricsApp: React.FC<{
|
|||
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
|
||||
storage: Storage;
|
||||
theme$: AppMountParameters['theme$'];
|
||||
kibanaEnvironment: KibanaEnvContext;
|
||||
}> = ({
|
||||
core,
|
||||
history,
|
||||
|
@ -75,11 +79,18 @@ const MetricsApp: React.FC<{
|
|||
setHeaderActionMenu,
|
||||
storage,
|
||||
theme$,
|
||||
kibanaEnvironment,
|
||||
}) => {
|
||||
const uiCapabilities = core.application.capabilities;
|
||||
|
||||
return (
|
||||
<CoreProviders core={core} pluginStart={pluginStart} plugins={plugins} theme$={theme$}>
|
||||
<CoreProviders
|
||||
core={core}
|
||||
pluginStart={pluginStart}
|
||||
plugins={plugins}
|
||||
theme$={theme$}
|
||||
kibanaEnvironment={kibanaEnvironment}
|
||||
>
|
||||
<CommonInfraProviders
|
||||
appName="Metrics UI"
|
||||
setHeaderActionMenu={setHeaderActionMenu}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { PropsOf } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, createElement, createContext } from 'react';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import {
|
||||
createKibanaReactContext,
|
||||
|
@ -17,6 +17,12 @@ import { InfraClientCoreSetup, InfraClientStartDeps, InfraClientStartExports } f
|
|||
|
||||
export type PluginKibanaContextValue = CoreStart & InfraClientStartDeps & InfraClientStartExports;
|
||||
|
||||
export interface KibanaEnvContext {
|
||||
kibanaVersion?: string;
|
||||
isCloudEnv?: boolean;
|
||||
isServerlessEnv?: boolean;
|
||||
}
|
||||
|
||||
export const createKibanaContextForPlugin = (
|
||||
core: CoreStart,
|
||||
plugins: InfraClientStartDeps,
|
||||
|
@ -28,6 +34,8 @@ export const createKibanaContextForPlugin = (
|
|||
...pluginStart,
|
||||
});
|
||||
|
||||
export const KibanaEnvironmentContext = createContext<KibanaEnvContext>({});
|
||||
|
||||
export const useKibanaContextForPlugin =
|
||||
useKibana as () => KibanaReactContextValue<PluginKibanaContextValue>;
|
||||
|
||||
|
@ -40,6 +48,27 @@ export const useKibanaContextForPluginProvider = (
|
|||
() => createKibanaContextForPlugin(core, plugins, pluginStart),
|
||||
[core, pluginStart, plugins]
|
||||
);
|
||||
return Provider;
|
||||
};
|
||||
|
||||
export const useKibanaEnvironmentContextProvider = (kibanaEnvironment?: KibanaEnvContext) => {
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
kibanaVersion: kibanaEnvironment?.kibanaVersion,
|
||||
isCloudEnv: kibanaEnvironment?.isCloudEnv,
|
||||
isServerlessEnv: kibanaEnvironment?.isServerlessEnv,
|
||||
}),
|
||||
[kibanaEnvironment]
|
||||
);
|
||||
|
||||
const Provider: React.FC<{ kibanaEnv?: KibanaEnvContext }> = ({ kibanaEnv = {}, children }) => {
|
||||
const newProvider = createElement(KibanaEnvironmentContext.Provider, {
|
||||
value: { ...kibanaEnv, ...value },
|
||||
children,
|
||||
});
|
||||
|
||||
return newProvider;
|
||||
};
|
||||
|
||||
return Provider;
|
||||
};
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { useTrackPageview } from '@kbn/observability-shared-plugin/public';
|
||||
import React, { useContext } from 'react';
|
||||
import { useTrackPageview, FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public';
|
||||
import { APP_WRAPPER_CLASS } from '@kbn/core/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FeatureFeedbackButton } from '../../../components/feature_feedback_button';
|
||||
import { KibanaEnvironmentContext } from '../../../hooks/use_kibana';
|
||||
import { SourceErrorPage } from '../../../components/source_error_page';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { useSourceContext } from '../../../containers/metrics_source';
|
||||
|
@ -29,6 +29,7 @@ const HOSTS_FEEDBACK_LINK =
|
|||
|
||||
export const HostsPage = () => {
|
||||
const { isLoading, loadSourceFailureMessage, loadSource, source } = useSourceContext();
|
||||
const { kibanaVersion, isCloudEnv, isServerlessEnv } = useContext(KibanaEnvironmentContext);
|
||||
|
||||
useTrackPageview({ app: 'infra_metrics', path: 'hosts' });
|
||||
useTrackPageview({ app: 'infra_metrics', path: 'hosts', delay: 15000 });
|
||||
|
@ -84,6 +85,9 @@ export const HostsPage = () => {
|
|||
<FeatureFeedbackButton
|
||||
data-test-subj="infraHostsPageTellUsWhatYouThinkButton"
|
||||
formUrl={HOSTS_FEEDBACK_LINK}
|
||||
kibanaVersion={kibanaVersion}
|
||||
isCloudEnv={isCloudEnv}
|
||||
isServerlessEnv={isServerlessEnv}
|
||||
/>,
|
||||
],
|
||||
}}
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiGlobalToastList } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import useLocalStorage from 'react-use/lib/useLocalStorage';
|
||||
import { FeatureFeedbackButton } from '../../../../components/feature_feedback_button';
|
||||
import { FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public';
|
||||
import { KibanaEnvironmentContext } from '../../../../hooks/use_kibana';
|
||||
|
||||
const KUBERNETES_TOAST_STORAGE_KEY = 'kubernetesToastKey';
|
||||
const KUBERNETES_FEEDBACK_LINK = 'https://ela.st/k8s-feedback';
|
||||
|
@ -18,11 +19,16 @@ export const SurveyKubernetes = () => {
|
|||
const [isToastSeen, setIsToastSeen] = useLocalStorage(KUBERNETES_TOAST_STORAGE_KEY, false);
|
||||
const markToastAsSeen = () => setIsToastSeen(true);
|
||||
|
||||
const { kibanaVersion, isCloudEnv, isServerlessEnv } = useContext(KibanaEnvironmentContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FeatureFeedbackButton
|
||||
formUrl={KUBERNETES_FEEDBACK_LINK}
|
||||
data-test-subj="infra-kubernetes-feedback-link"
|
||||
kibanaVersion={kibanaVersion}
|
||||
isCloudEnv={isCloudEnv}
|
||||
isServerlessEnv={isServerlessEnv}
|
||||
surveyButtonText={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.homePage.tellUsWhatYouThinkK8sLink"
|
||||
|
@ -60,6 +66,9 @@ export const SurveyKubernetes = () => {
|
|||
data-test-subj="infra-toast-kubernetes-survey-start"
|
||||
onClickCapture={markToastAsSeen}
|
||||
defaultButton={true}
|
||||
kibanaVersion={kibanaVersion}
|
||||
isCloudEnv={isCloudEnv}
|
||||
isServerlessEnv={isServerlessEnv}
|
||||
surveyButtonText={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.homePage.kubernetesToastButton"
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FeatureFeedbackButton } from '../../../../components/feature_feedback_button';
|
||||
import React, { useContext } from 'react';
|
||||
import { FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public';
|
||||
import { KibanaEnvironmentContext } from '../../../../hooks/use_kibana';
|
||||
|
||||
import { useWaffleOptionsContext } from '../hooks/use_waffle_options';
|
||||
import { SurveyKubernetes } from './survey_kubernetes';
|
||||
|
@ -15,6 +16,7 @@ const INVENTORY_FEEDBACK_LINK = 'https://ela.st/survey-infra-inventory?usp=pp_ur
|
|||
|
||||
export const SurveySection = () => {
|
||||
const { nodeType } = useWaffleOptionsContext();
|
||||
const { kibanaVersion, isCloudEnv, isServerlessEnv } = useContext(KibanaEnvironmentContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -24,6 +26,9 @@ export const SurveySection = () => {
|
|||
<FeatureFeedbackButton
|
||||
data-test-subj="infraInventoryFeedbackLink"
|
||||
formUrl={INVENTORY_FEEDBACK_LINK}
|
||||
kibanaVersion={kibanaVersion}
|
||||
isCloudEnv={isCloudEnv}
|
||||
isServerlessEnv={isServerlessEnv}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTrackPageview } from '@kbn/observability-shared-plugin/public';
|
||||
import { FeatureFeedbackButton } from '../../../components/feature_feedback_button';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useTrackPageview, FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public';
|
||||
import { KibanaEnvironmentContext } from '../../../hooks/use_kibana';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { useMetricsExplorerViews } from '../../../hooks/use_metrics_explorer_views';
|
||||
import { MetricsSourceConfigurationProperties } from '../../../../common/metrics_sources';
|
||||
|
@ -52,6 +52,7 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl
|
|||
} = useMetricsExplorerState(source, derivedIndexPattern, enabled);
|
||||
const { currentView } = useMetricsExplorerViews();
|
||||
const { source: sourceContext, metricIndicesExist } = useSourceContext();
|
||||
const { kibanaVersion, isCloudEnv, isServerlessEnv } = useContext(KibanaEnvironmentContext);
|
||||
|
||||
useTrackPageview({ app: 'infra_metrics', path: 'metrics_explorer' });
|
||||
useTrackPageview({ app: 'infra_metrics', path: 'metrics_explorer', delay: 15000 });
|
||||
|
@ -100,6 +101,9 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl
|
|||
<FeatureFeedbackButton
|
||||
formUrl={METRICS_EXPLORER_FEEDBACK_URL}
|
||||
data-test-subj="infraMetricsExplorerFeedbackLink"
|
||||
kibanaVersion={kibanaVersion}
|
||||
isCloudEnv={isCloudEnv}
|
||||
isServerlessEnv={isServerlessEnv}
|
||||
/>,
|
||||
],
|
||||
}}
|
||||
|
|
|
@ -314,13 +314,11 @@ export class Plugin implements InfraClientPluginClass {
|
|||
|
||||
const isCloudEnv = !!pluginsSetup.cloud?.isCloudEnabled;
|
||||
const isServerlessEnv = pluginsSetup.cloud?.isServerlessEnabled || this.isServerlessEnv;
|
||||
return renderApp(
|
||||
coreStart,
|
||||
{ ...plugins, kibanaVersion: this.kibanaVersion, isCloudEnv, isServerlessEnv },
|
||||
pluginStart,
|
||||
this.config,
|
||||
params
|
||||
);
|
||||
return renderApp(coreStart, { ...plugins }, pluginStart, this.config, params, {
|
||||
kibanaVersion: this.kibanaVersion,
|
||||
isCloudEnv,
|
||||
isServerlessEnv,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -88,9 +88,6 @@ export interface InfraClientStartDeps {
|
|||
dataViews: DataViewsPublicPluginStart;
|
||||
discover: DiscoverStart;
|
||||
embeddable?: EmbeddableStart;
|
||||
kibanaVersion?: string;
|
||||
isCloudEnv: boolean;
|
||||
isServerlessEnv: boolean;
|
||||
lens: LensPublicStart;
|
||||
logsShared: LogsSharedClientStartExports;
|
||||
ml: MlPluginStart;
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
import React, { ReactElement } from 'react';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useKibanaContextForPlugin } from '../hooks/use_kibana';
|
||||
|
||||
const KIBANA_VERSION_QUERY_PARAM = 'entry.548460210';
|
||||
const KIBANA_DEPLOYMENT_TYPE_PARAM = 'entry.573002982';
|
||||
const SANITIZED_PATH_PARAM = 'entry.1876422621';
|
||||
|
||||
const getDeploymentType = (isCloudEnv: boolean, isServerlessEnv: boolean): string | undefined => {
|
||||
const getDeploymentType = (isCloudEnv?: boolean, isServerlessEnv?: boolean): string | undefined => {
|
||||
if (isServerlessEnv) {
|
||||
return 'Serverless (fully-managed projects)';
|
||||
}
|
||||
|
@ -23,7 +23,12 @@ const getDeploymentType = (isCloudEnv: boolean, isServerlessEnv: boolean): strin
|
|||
return 'Self-Managed (you manage)';
|
||||
};
|
||||
|
||||
const getSurveyFeedbackURL = (formUrl: string, kibanaVersion?: string, deploymentType?: string) => {
|
||||
const getSurveyFeedbackURL = (
|
||||
formUrl: string,
|
||||
kibanaVersion?: string,
|
||||
deploymentType?: string,
|
||||
sanitizedPath?: string
|
||||
) => {
|
||||
const url = new URL(formUrl);
|
||||
if (kibanaVersion) {
|
||||
url.searchParams.append(KIBANA_VERSION_QUERY_PARAM, kibanaVersion);
|
||||
|
@ -31,6 +36,9 @@ const getSurveyFeedbackURL = (formUrl: string, kibanaVersion?: string, deploymen
|
|||
if (deploymentType) {
|
||||
url.searchParams.append(KIBANA_DEPLOYMENT_TYPE_PARAM, deploymentType);
|
||||
}
|
||||
if (sanitizedPath) {
|
||||
url.searchParams.append(SANITIZED_PATH_PARAM, sanitizedPath);
|
||||
}
|
||||
|
||||
return url.href;
|
||||
};
|
||||
|
@ -41,6 +49,10 @@ interface FeatureFeedbackButtonProps {
|
|||
surveyButtonText?: ReactElement;
|
||||
onClickCapture?: () => void;
|
||||
defaultButton?: boolean;
|
||||
kibanaVersion?: string;
|
||||
isCloudEnv?: boolean;
|
||||
isServerlessEnv?: boolean;
|
||||
sanitizedPath?: string;
|
||||
}
|
||||
|
||||
export const FeatureFeedbackButton = ({
|
||||
|
@ -48,21 +60,25 @@ export const FeatureFeedbackButton = ({
|
|||
'data-test-subj': dts,
|
||||
onClickCapture,
|
||||
defaultButton,
|
||||
kibanaVersion,
|
||||
isCloudEnv,
|
||||
isServerlessEnv,
|
||||
sanitizedPath,
|
||||
surveyButtonText = (
|
||||
<FormattedMessage
|
||||
id="xpack.infra.homePage.tellUsWhatYouThinkLink"
|
||||
id="xpack.observabilityShared.featureFeedbackButton.tellUsWhatYouThinkLink"
|
||||
defaultMessage="Tell us what you think!"
|
||||
/>
|
||||
),
|
||||
}: FeatureFeedbackButtonProps) => {
|
||||
const {
|
||||
services: { kibanaVersion, isCloudEnv, isServerlessEnv },
|
||||
} = useKibanaContextForPlugin();
|
||||
const deploymentType =
|
||||
isCloudEnv !== undefined || isServerlessEnv !== undefined
|
||||
? getDeploymentType(isCloudEnv, isServerlessEnv)
|
||||
: undefined;
|
||||
|
||||
const deploymentType = getDeploymentType(isCloudEnv, isServerlessEnv);
|
||||
return (
|
||||
<EuiButton
|
||||
href={getSurveyFeedbackURL(formUrl, kibanaVersion, deploymentType)}
|
||||
href={getSurveyFeedbackURL(formUrl, kibanaVersion, deploymentType, sanitizedPath)}
|
||||
target="_blank"
|
||||
color={defaultButton ? undefined : 'warning'}
|
||||
iconType={defaultButton ? undefined : 'editorComment'}
|
|
@ -92,3 +92,4 @@ export {
|
|||
} from './components/profiling/embeddables';
|
||||
|
||||
export { ProfilingEmptyState } from './components/profiling/profiling_empty_state';
|
||||
export { FeatureFeedbackButton } from './components/feature_feedback_button/feature_feedback_button';
|
||||
|
|
|
@ -20158,7 +20158,7 @@
|
|||
"xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "Voir les instructions de configuration",
|
||||
"xpack.infra.homePage.settingsTabTitle": "Paramètres",
|
||||
"xpack.infra.homePage.tellUsWhatYouThinkK8sLink": "Dites-nous ce que vous pensez ! (K8s)",
|
||||
"xpack.infra.homePage.tellUsWhatYouThinkLink": "Dites-nous ce que vous pensez !",
|
||||
"xpack.observabilityShared.featureFeedbackButton.tellUsWhatYouThinkLink": "Dites-nous ce que vous pensez !",
|
||||
"xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "Rechercher des données d'infrastructure… (par exemple host.name:host-1)",
|
||||
"xpack.infra.hostFlyout.explainProcessMessageTitle": "Quel est ce processus ?",
|
||||
"xpack.infra.hosts.searchPlaceholder": "Rechercher dans les hôtes (par ex. cloud.provider:gcp AND system.load.1 > 0.5)",
|
||||
|
|
|
@ -20171,7 +20171,7 @@
|
|||
"xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "セットアップの手順を表示",
|
||||
"xpack.infra.homePage.settingsTabTitle": "設定",
|
||||
"xpack.infra.homePage.tellUsWhatYouThinkK8sLink": "ご意見をお聞かせください。(K8s)",
|
||||
"xpack.infra.homePage.tellUsWhatYouThinkLink": "ご意見をお聞かせください。",
|
||||
"xpack.observabilityShared.featureFeedbackButton.tellUsWhatYouThinkLink": "ご意見をお聞かせください。",
|
||||
"xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "インフラストラクチャーデータを検索…(例:host.name:host-1)",
|
||||
"xpack.infra.hostFlyout.explainProcessMessageTitle": "このプロセスの概要",
|
||||
"xpack.infra.hosts.searchPlaceholder": "ホストを検索(例:cloud.provider:gcp AND system.load.1 > 0.5)",
|
||||
|
|
|
@ -20238,7 +20238,7 @@
|
|||
"xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "查看设置说明",
|
||||
"xpack.infra.homePage.settingsTabTitle": "设置",
|
||||
"xpack.infra.homePage.tellUsWhatYouThinkK8sLink": "告诉我们您的看法!(K8s)",
|
||||
"xpack.infra.homePage.tellUsWhatYouThinkLink": "告诉我们您的看法!",
|
||||
"xpack.observabilityShared.featureFeedbackButton.tellUsWhatYouThinkLink": "告诉我们您的看法!",
|
||||
"xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "搜索基础设施数据……(例如 host.name:host-1)",
|
||||
"xpack.infra.hostFlyout.explainProcessMessageTitle": "此进程是什么?",
|
||||
"xpack.infra.hosts.searchPlaceholder": "搜索主机(例如,cloud.provider:gcp AND system.load.1 > 0.5)",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue