[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/form




52cc886f-95a9-4099-b047-93fc64036572
This commit is contained in:
jennypavlova 2024-01-05 14:52:32 +01:00 committed by GitHub
parent d458b5382f
commit 35d037b2d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 356 additions and 84 deletions

View file

@ -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

View file

@ -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>

View file

@ -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>
);

View file

@ -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);

View file

@ -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>({});

View file

@ -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;
};

View file

@ -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: {

View file

@ -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);
}
);
});

View 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}*`;
};

View file

@ -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>
);
};

View file

@ -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}

View file

@ -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;
};

View file

@ -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}
/>,
],
}}

View file

@ -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"

View file

@ -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}
/>
)}
</>

View file

@ -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}
/>,
],
}}

View file

@ -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,
});
},
});

View file

@ -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;

View file

@ -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'}

View file

@ -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';

View file

@ -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)",

View file

@ -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",

View file

@ -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",