[8.11] [SecuritySolution] Remove @kbn/subscription-tracking (#171801) (#171884)

# Backport

This will backport the following commits from `main` to `8.11`:
- [[SecuritySolution] Remove `@kbn/subscription-tracking`
(#171801)](https://github.com/elastic/kibana/pull/171801)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Jan
Monschke","email":"jan.monschke@elastic.co"},"sourceCommit":{"committedDate":"2023-11-23T15:39:23Z","message":"[SecuritySolution]
Remove `@kbn/subscription-tracking` (#171801)\n\n## Summary\r\n\r\nThe
package data isn't needed anymore, so we can remove that package.
On\r\ntop, it seems like the package was causing some
issues.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"df16cd9c2cfc08bc999f436a11913df50e71ce19","branchLabelMapping":{"^v8.12.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:Threat
Hunting:Investigations","backport:prev-minor","v8.12.0","v8.11.2"],"number":171801,"url":"https://github.com/elastic/kibana/pull/171801","mergeCommit":{"message":"[SecuritySolution]
Remove `@kbn/subscription-tracking` (#171801)\n\n## Summary\r\n\r\nThe
package data isn't needed anymore, so we can remove that package.
On\r\ntop, it seems like the package was causing some
issues.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"df16cd9c2cfc08bc999f436a11913df50e71ce19"}},"sourceBranch":"main","suggestedTargetBranches":["8.11"],"targetPullRequestStates":[{"branch":"main","label":"v8.12.0","labelRegex":"^v8.12.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/171801","number":171801,"mergeCommit":{"message":"[SecuritySolution]
Remove `@kbn/subscription-tracking` (#171801)\n\n## Summary\r\n\r\nThe
package data isn't needed anymore, so we can remove that package.
On\r\ntop, it seems like the package was causing some
issues.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"df16cd9c2cfc08bc999f436a11913df50e71ce19"}},{"branch":"8.11","label":"v8.11.2","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Jan Monschke 2023-11-24 14:36:26 +01:00 committed by GitHub
parent 5559c9b84c
commit 350d069b00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 96 additions and 780 deletions

View file

@ -733,7 +733,6 @@
"@kbn/status-plugin-a-plugin": "link:test/server_integration/plugins/status_plugin_a",
"@kbn/status-plugin-b-plugin": "link:test/server_integration/plugins/status_plugin_b",
"@kbn/std": "link:packages/kbn-std",
"@kbn/subscription-tracking": "link:packages/kbn-subscription-tracking",
"@kbn/synthetics-plugin": "link:x-pack/plugins/synthetics",
"@kbn/task-manager-fixture-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture",
"@kbn/task-manager-performance-plugin": "link:x-pack/test/plugin_api_perf/plugins/task_manager_performance",

View file

@ -1,35 +0,0 @@
# @kbn/subscription-tracking
This package leverages the `@kbn/analytics-client` package to send dedicated subscription tracking events.
It provides a set of React components that automatically track `impression` and `click` events. Consumers of those components need to specify a `subscription context` that gives more details on the type of feature that is advertised and the location of the upsell.
```typescript
import { SubscriptionLink } from '@kbn/subscription-tracking';
import type { SubscriptionContext } from '@kbn/subscription-tracking';
const subscriptionContext: SubscriptionContext = {
feature: 'threat-intelligence',
source: 'security__threat-intelligence',
};
export const Paywall = () => {
return (
<div>
<SubscriptionLink subscriptionContext={subscriptionContext}>
Upgrade to Platinum to get this feature
</SubscriptionLink>
</div>
);
};
```
The example above uses a `SubscriptionLink` which is a wrapper of `EuiLink` . So it behaves just like a normal link. Alternatively, upsells can also use a `SubscriptionButton` or `SubscriptionButtonEmpty` which wrap `EuiButton` and `EuiButtonEmpty` respectively.
When the link is mounted, it will send off an `impression` event with the given `subscriptionContext`. That piece of metadata consists of an identifier of the advertised feature (in this case `threat-intelligence`) and the `source` (aka location) of the impression (in this case the `threat-intelligence` page in the `security` solution). `source` follows the following format: `{solution-identifier}__location-identifier`.
There are no special rules for how to name these identifiers but it's good practise to make sure that `feature` has the same value for all upsells advertising the same feature (e.g. use enums for features to prevent spelling mistakes).
Upon interaction with the upsell link/button, a special `click` event is sent, which, again, contains the same subscription context.
If you want to use the `subscription-tracking` elements in your app, you have to set up a `SubscriptionTrackingProvider` in your plugin setup and register the tracking events on startup. Have a look at https://github.com/elastic/kibana/pull/143910 for an example of an integration.

View file

@ -1,17 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './src/subscription_elements';
export {
SubscriptionTrackingContext,
SubscriptionTrackingProvider,
registerEvents,
} from './src/services';
export * from './types';

View file

@ -1,13 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-subscription-tracking'],
};

View file

@ -1,5 +0,0 @@
{
"type": "shared-common",
"id": "@kbn/subscription-tracking",
"owner": "@elastic/security-threat-hunting-investigations"
}

View file

@ -1,28 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { FC } from 'react';
import { analyticsClientMock } from '@kbn/analytics-client/src/mocks';
import { SubscriptionTrackingProvider } from './src/services';
const analyticsClientMockInst = analyticsClientMock.create();
/**
* Mock for the external services provider. Only use in tests!
*/
export const MockSubscriptionTrackingProvider: FC = ({ children }) => {
return (
<SubscriptionTrackingProvider
navigateToApp={jest.fn()}
analyticsClient={analyticsClientMockInst}
>
{children}
</SubscriptionTrackingProvider>
);
};

View file

@ -1,6 +0,0 @@
{
"name": "@kbn/subscription-tracking",
"private": true,
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0"
}

View file

@ -1,45 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { isValidContext } from './helpers';
describe('tracking', () => {
describe('isValidLocation', () => {
it('identifies correct contexts', () => {
expect(
isValidContext({
feature: 'test',
source: 'security__test',
})
).toBeTruthy();
});
it('identifies incorrect contexts', () => {
expect(
isValidContext({
feature: '',
source: 'security__',
})
).toBeFalsy();
expect(
isValidContext({
feature: 'test',
source: 'security__',
})
).toBeFalsy();
expect(
isValidContext({
feature: '',
source: 'security__test',
})
).toBeFalsy();
});
});
});

View file

@ -1,14 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { SubscriptionContextData } from '../types';
const sourceStringRegEx = /^(\w[\w\-_]*)__(\w[\w\-_]*)$/;
export function isValidContext(context: SubscriptionContextData): boolean {
return context.feature.length > 0 && sourceStringRegEx.test(context.source);
}

View file

@ -1,70 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { FC, useContext } from 'react';
import type { AnalyticsClient, EventTypeOpts } from '@kbn/analytics-client';
import { EVENT_NAMES, Services, SubscriptionContextData } from '../types';
export const SubscriptionTrackingContext = React.createContext<Services | null>(null);
/**
* External services provider
*/
export const SubscriptionTrackingProvider: FC<Services> = ({ children, ...services }) => {
return (
<SubscriptionTrackingContext.Provider value={services}>
{children}
</SubscriptionTrackingContext.Provider>
);
};
/**
* React hook for accessing pre-wired services.
*/
export function useServices() {
const context = useContext(SubscriptionTrackingContext);
if (!context) {
throw new Error(
'SubscriptionTrackingContext is missing. Ensure your component or React root is wrapped with SubscriptionTrackingProvider.'
);
}
return context;
}
const subscriptionContextSchema: EventTypeOpts<SubscriptionContextData>['schema'] = {
source: {
type: 'keyword',
_meta: {
description:
'A human-readable identifier describing the location of the beginning of the subscription flow',
},
},
feature: {
type: 'keyword',
_meta: {
description: 'A human-readable identifier describing the feature that is being promoted',
},
},
};
/**
* Registers the subscription-specific event types
*/
export function registerEvents(analyticsClient: Pick<AnalyticsClient, 'registerEventType'>) {
analyticsClient.registerEventType<SubscriptionContextData>({
eventType: EVENT_NAMES.IMPRESSION,
schema: subscriptionContextSchema,
});
analyticsClient.registerEventType<SubscriptionContextData>({
eventType: EVENT_NAMES.CLICK,
schema: subscriptionContextSchema,
});
}

View file

@ -1,108 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { render, screen } from '@testing-library/react';
import {
SubscriptionLink,
SubscriptionButton,
SubscriptionButtonEmpty,
} from './subscription_elements';
import { SubscriptionTrackingProvider } from './services';
import { EVENT_NAMES, Services, SubscriptionContextData } from '../types';
import { coolDownTimeMs, resetCoolDown } from './use_impression';
const testServices: Services = {
navigateToApp: jest.fn(),
analyticsClient: {
reportEvent: jest.fn(),
registerEventType: jest.fn(),
} as any,
};
const testContext: SubscriptionContextData = { feature: 'test', source: 'security__test' };
const WithProviders: React.FC = ({ children }) => (
<SubscriptionTrackingProvider
analyticsClient={testServices.analyticsClient}
navigateToApp={testServices.navigateToApp}
>
{children}
</SubscriptionTrackingProvider>
);
const renderWithProviders = (children: React.ReactElement) =>
render(children, { wrapper: WithProviders });
const reset = () => {
jest.resetAllMocks();
resetCoolDown();
};
describe('SubscriptionElements', () => {
beforeAll(() => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
});
[SubscriptionButton, SubscriptionLink, SubscriptionButtonEmpty].forEach((SubscriptionElement) => {
describe(SubscriptionElement.name, () => {
beforeEach(reset);
it('renders the children correctly', () => {
renderWithProviders(
<SubscriptionElement subscriptionContext={testContext}>Hello</SubscriptionElement>
);
expect(screen.getByText('Hello')).toBeTruthy();
});
it('fires an impression event when rendered', () => {
renderWithProviders(<SubscriptionElement subscriptionContext={testContext} />);
expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledWith(
EVENT_NAMES.IMPRESSION,
testContext
);
});
it('fires an impression event when rendered (but only once)', () => {
const { unmount } = renderWithProviders(
<SubscriptionElement subscriptionContext={testContext} />
);
expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledTimes(1);
unmount();
// does not create an impression again when remounted
const { unmount: unmountAgain } = renderWithProviders(
<SubscriptionElement subscriptionContext={testContext} />
);
unmountAgain();
expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledTimes(1);
// only creates anew impression when the cooldown time has passed
jest.setSystemTime(Date.now() + coolDownTimeMs);
renderWithProviders(<SubscriptionElement subscriptionContext={testContext} />);
expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledTimes(2);
});
it('tracks a click when clicked and navigates to page', () => {
renderWithProviders(
<SubscriptionElement subscriptionContext={testContext}>hello</SubscriptionElement>
);
screen.getByText('hello').click();
expect(testServices.analyticsClient.reportEvent).toHaveBeenCalledWith(
EVENT_NAMES.CLICK,
testContext
);
expect(testServices.navigateToApp).toHaveBeenCalled();
});
});
});
});

View file

@ -1,79 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { EuiLink, EuiButton, EuiButtonEmpty } from '@elastic/eui';
import type { EuiLinkProps, EuiButtonEmptyProps, EuiButtonProps } from '@elastic/eui';
import { useGoToSubscription } from './use_go_to_subscription';
import { useImpression } from './use_impression';
import type { SubscriptionContextData } from '../types';
interface CommonProps {
/** The context information for this subscription element */
subscriptionContext: SubscriptionContextData;
}
export type SubscriptionLinkProps = EuiLinkProps & CommonProps;
/**
* Wrapper around `EuiLink` that provides subscription events
*/
export function SubscriptionLink({
subscriptionContext,
children,
...restProps
}: SubscriptionLinkProps) {
const goToSubscription = useGoToSubscription({ subscriptionContext });
useImpression(subscriptionContext);
return (
<EuiLink {...restProps} onClick={goToSubscription}>
{children}
</EuiLink>
);
}
export type SubscriptionButtonProps = EuiButtonProps & CommonProps;
/**
* Wrapper around `EuiButton` that provides subscription events
*/
export function SubscriptionButton({
subscriptionContext,
children,
...restProps
}: SubscriptionButtonProps) {
const goToSubscription = useGoToSubscription({ subscriptionContext });
useImpression(subscriptionContext);
return (
<EuiButton {...restProps} onClick={goToSubscription}>
{children}
</EuiButton>
);
}
export type SubscriptionButtonEmptyProps = EuiButtonEmptyProps & CommonProps;
/**
* Wrapper around `EuiButtonEmpty` that provides subscription events
*/
export function SubscriptionButtonEmpty({
subscriptionContext,
children,
...restProps
}: SubscriptionButtonEmptyProps) {
const goToSubscription = useGoToSubscription({ subscriptionContext });
useImpression(subscriptionContext);
return (
<EuiButtonEmpty {...restProps} onClick={goToSubscription}>
{children}
</EuiButtonEmpty>
);
}

View file

@ -1,35 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { useCallback } from 'react';
import { isValidContext } from './helpers';
import { useServices } from './services';
import { EVENT_NAMES, SubscriptionContextData } from '../types';
interface Options {
subscriptionContext: SubscriptionContextData;
}
/**
* Provides a navigation function that navigates to the subscription
* management page. When the function executes, a click event with the
* given context is emitted.
*/
export const useGoToSubscription = ({ subscriptionContext }: Options) => {
const { navigateToApp, analyticsClient } = useServices();
const goToSubscription = useCallback(() => {
if (isValidContext(subscriptionContext)) {
analyticsClient.reportEvent(EVENT_NAMES.CLICK, subscriptionContext);
} else {
// eslint-disable-next-line no-console
console.error('The provided subscription context is invalid', subscriptionContext);
}
navigateToApp('management', { path: 'stack/license_management' });
}, [analyticsClient, navigateToApp, subscriptionContext]);
return goToSubscription;
};

View file

@ -1,57 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { useEffect } from 'react';
import { isValidContext } from './helpers';
import { useServices } from './services';
import { EVENT_NAMES, SubscriptionContextData } from '../types';
/**
* Sends an impression event with the given context.
*
* Note: impression events are throttled and will not fire more
* often than once every 30 seconds.
*/
export const useImpression = (context: SubscriptionContextData) => {
const { analyticsClient } = useServices();
useEffect(() => {
if (!isValidContext(context)) {
// eslint-disable-next-line no-console
console.error('The provided subscription context is invalid', context);
return;
}
if (!isCoolingDown(context)) {
analyticsClient.reportEvent(EVENT_NAMES.IMPRESSION, context);
coolDown(context);
}
}, [analyticsClient, context]);
};
/**
* Impressions from the same context should not fire more than once every 30 seconds.
* This prevents logging too many impressions in case a page is reloaded often or
* if the user is navigating back and forth rapidly.
*/
export const coolDownTimeMs = 30 * 1000;
let impressionCooldown = new WeakMap<SubscriptionContextData, number>();
function isCoolingDown(context: SubscriptionContextData) {
const previousLog = impressionCooldown.get(context);
// we logged before and we are in the cooldown period
return previousLog && Date.now() - previousLog < coolDownTimeMs;
}
function coolDown(context: SubscriptionContextData) {
impressionCooldown.set(context, Date.now());
}
export function resetCoolDown() {
impressionCooldown = new WeakMap<SubscriptionContextData, number>();
}

View file

@ -1,10 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": ["jest", "node", "react"]
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["target/**/*"],
"kbn_references": ["@kbn/analytics-client"]
}

View file

@ -1,47 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { AnalyticsClient } from '@kbn/analytics-client';
enum SolutionIdentifier {
observability = 'observability',
security = 'security',
}
type LocationString = string;
type SourceIdentifier = `${SolutionIdentifier}__${LocationString}`;
/**
* A piece of metadata which consists of an identifier of the advertised feature and
* the `source` (e.g. location) of the subscription element.
*/
export interface SubscriptionContextData {
/**
* A human-readable identifier describing the location of the beginning of the
* subscription flow.
* Location identifiers are prefixed with a solution identifier, e.g. `security__`
*
* @example "security__host-overview" - the user is looking at an upsell button
* on the host overview page in the security solution
*/
source: SourceIdentifier;
/**
* A human-readable identifier describing the feature that is being promoted.
*
* @example "alerts-by-process-ancestry"
*/
feature: string;
}
export interface Services {
navigateToApp: (app: string, options: { path: string }) => void;
analyticsClient: Pick<AnalyticsClient, 'reportEvent'>;
}
export enum EVENT_NAMES {
CLICK = 'subscription__upsell__click',
IMPRESSION = 'subscription__upsell__impression',
}

View file

@ -1462,8 +1462,6 @@
"@kbn/stdio-dev-helpers/*": ["packages/kbn-stdio-dev-helpers/*"],
"@kbn/storybook": ["packages/kbn-storybook"],
"@kbn/storybook/*": ["packages/kbn-storybook/*"],
"@kbn/subscription-tracking": ["packages/kbn-subscription-tracking"],
"@kbn/subscription-tracking/*": ["packages/kbn-subscription-tracking/*"],
"@kbn/synthetics-plugin": ["x-pack/plugins/synthetics"],
"@kbn/synthetics-plugin/*": ["x-pack/plugins/synthetics/*"],
"@kbn/task-manager-fixture-plugin": ["x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture"],
@ -1638,9 +1636,7 @@
"@kbn/yarn-lock-validator/*": ["packages/kbn-yarn-lock-validator/*"],
// END AUTOMATED PACKAGE LISTING
// Allows for importing from `kibana` package for the exported types.
"@emotion/core": [
"typings/@emotion"
],
"@emotion/core": ["typings/@emotion"]
},
// Support .tsx files and transform JSX into calls to React.createElement
"jsx": "react",

View file

@ -6,15 +6,8 @@
*/
import React from 'react';
import { EuiEmptyPrompt, EuiPageSection } from '@elastic/eui';
import { EuiEmptyPrompt, EuiLink, EuiPageSection } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { SubscriptionLink } from '@kbn/subscription-tracking';
import type { SubscriptionContextData } from '@kbn/subscription-tracking';
const subscriptionContext: SubscriptionContextData = {
feature: 'cloud-security-posture',
source: 'security__cloud-security-posture',
};
export const SubscriptionNotAllowed = ({
licenseManagementLocator,
@ -41,12 +34,12 @@ export const SubscriptionNotAllowed = ({
defaultMessage="To use these cloud security features, you must {link}."
values={{
link: (
<SubscriptionLink subscriptionContext={subscriptionContext}>
<EuiLink href={licenseManagementLocator}>
<FormattedMessage
id="xpack.csp.subscriptionNotAllowed.promptLinkText"
defaultMessage="start a trial or upgrade your subscription"
/>
</SubscriptionLink>
</EuiLink>
),
}}
/>

View file

@ -11,7 +11,6 @@ import { I18nProvider } from '@kbn/i18n-react';
// eslint-disable-next-line no-restricted-imports
import { Router } from 'react-router-dom';
import { Route, Routes } from '@kbn/shared-ux-router';
import { MockSubscriptionTrackingProvider } from '@kbn/subscription-tracking/mocks';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { coreMock } from '@kbn/core/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
@ -52,11 +51,9 @@ export const TestProvider: React.FC<Partial<CspAppDeps>> = ({
<QueryClientProvider client={queryClient}>
<Router history={params.history}>
<I18nProvider>
<MockSubscriptionTrackingProvider>
<Routes>
<Route path="*" render={() => <>{children}</>} />
</Routes>
</MockSubscriptionTrackingProvider>
<Routes>
<Route path="*" render={() => <>{children}</>} />
</Routes>
</I18nProvider>
</Router>
</QueryClientProvider>

View file

@ -50,7 +50,6 @@
"@kbn/share-plugin",
"@kbn/core-http-server",
"@kbn/core-http-browser",
"@kbn/subscription-tracking",
"@kbn/discover-utils",
"@kbn/unified-data-table",
"@kbn/cell-actions",

View file

@ -16,7 +16,6 @@ import { License } from '../common/license';
import { licenseMock } from '../common/licensing.mock';
import { coreMock } from '@kbn/core/public/mocks';
import { HttpInterceptor } from '@kbn/core/public';
import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser';
const coreStart = coreMock.createStart();
describe('licensing plugin', () => {
@ -443,69 +442,5 @@ describe('licensing plugin', () => {
expect(removeInterceptorMock).toHaveBeenCalledTimes(1);
});
it('registers the subscription upsell events', async () => {
const sessionStorage = coreMock.createStorage();
plugin = new LicensingPlugin(coreMock.createPluginInitializerContext(), sessionStorage);
const coreSetup = coreMock.createSetup();
await plugin.setup(coreSetup);
await plugin.stop();
expect(findRegisteredEventTypeByName('subscription__upsell__click', coreSetup.analytics))
.toMatchInlineSnapshot(`
Array [
Object {
"eventType": "subscription__upsell__click",
"schema": Object {
"feature": Object {
"_meta": Object {
"description": "A human-readable identifier describing the feature that is being promoted",
},
"type": "keyword",
},
"source": Object {
"_meta": Object {
"description": "A human-readable identifier describing the location of the beginning of the subscription flow",
},
"type": "keyword",
},
},
},
]
`);
expect(findRegisteredEventTypeByName('subscription__upsell__impression', coreSetup.analytics))
.toMatchInlineSnapshot(`
Array [
Object {
"eventType": "subscription__upsell__impression",
"schema": Object {
"feature": Object {
"_meta": Object {
"description": "A human-readable identifier describing the feature that is being promoted",
},
"type": "keyword",
},
"source": Object {
"_meta": Object {
"description": "A human-readable identifier describing the location of the beginning of the subscription flow",
},
"type": "keyword",
},
},
},
]
`);
});
});
});
function findRegisteredEventTypeByName(
eventTypeName: string,
analyticsClientMock: jest.Mocked<AnalyticsServiceSetup>
) {
return analyticsClientMock.registerEventType.mock.calls.find(
([{ eventType }]) => eventType === eventTypeName
)!;
}

View file

@ -8,7 +8,6 @@
import { Observable, Subject, Subscription } from 'rxjs';
import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';
import { registerEvents as registerSubscriptionTrackingEvents } from '@kbn/subscription-tracking';
import { ILicense } from '../common/types';
import { LicensingPluginSetup, LicensingPluginStart } from './types';
import { createLicenseUpdate } from '../common/license_update';
@ -85,7 +84,6 @@ export class LicensingPlugin implements Plugin<LicensingPluginSetup, LicensingPl
);
registerAnalyticsContextProvider(core.analytics, license$);
registerSubscriptionTrackingEvents(core.analytics);
this.internalSubscription = license$.subscribe((license) => {
if (license.isAvailable) {

View file

@ -14,8 +14,6 @@
"@kbn/std",
"@kbn/i18n",
"@kbn/analytics-client",
"@kbn/subscription-tracking",
"@kbn/core-analytics-browser"
],
"exclude": ["target/**/*"]
}

View file

@ -7,7 +7,6 @@
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { SubscriptionTrackingProvider } from '@kbn/subscription-tracking';
import { SecurityApp } from './app';
import type { RenderAppProps } from './types';
import { AppRoutes } from './app_routes';
@ -21,7 +20,6 @@ export const renderApp = ({
usageCollection,
subPluginRoutes,
theme$,
subscriptionTrackingServices,
}: RenderAppProps): (() => void) => {
const ApplicationUsageTrackingProvider =
usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment;
@ -34,12 +32,7 @@ export const renderApp = ({
theme$={theme$}
>
<ApplicationUsageTrackingProvider>
<SubscriptionTrackingProvider
analyticsClient={subscriptionTrackingServices.analyticsClient}
navigateToApp={subscriptionTrackingServices.navigateToApp}
>
<AppRoutes subPluginRoutes={subPluginRoutes} services={services} />
</SubscriptionTrackingProvider>
<AppRoutes subPluginRoutes={subPluginRoutes} services={services} />
</ApplicationUsageTrackingProvider>
</SecurityApp>,
element

View file

@ -19,7 +19,6 @@ import type { RouteProps } from 'react-router-dom';
import type { AppMountParameters } from '@kbn/core/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { TableState } from '@kbn/securitysolution-data-table';
import type { Services as SubscriptionTrackingServices } from '@kbn/subscription-tracking';
import type { ExploreReducer, ExploreState } from '../explore';
import type { StartServices } from '../types';
@ -30,7 +29,6 @@ export interface RenderAppProps extends AppMountParameters {
services: StartServices;
store: Store<State, Action>;
subPluginRoutes: RouteProps[];
subscriptionTrackingServices: SubscriptionTrackingServices;
usageCollection?: UsageCollectionSetup;
}

View file

@ -6,17 +6,11 @@
*/
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiText } from '@elastic/eui';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { SubscriptionLink } from '@kbn/subscription-tracking';
import type { SubscriptionContextData } from '@kbn/subscription-tracking';
import { INSIGHTS_UPSELL } from './translations';
const subscriptionContext: SubscriptionContextData = {
feature: 'alert-details-insights',
source: 'security__alert-details-flyout',
};
import { useKibana } from '../../../lib/kibana';
const UpsellContainer = euiStyled.div`
border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
@ -29,6 +23,7 @@ const StyledIcon = euiStyled(EuiIcon)`
`;
export const RelatedAlertsUpsell = React.memo(() => {
const { application } = useKibana().services;
return (
<UpsellContainer>
<EuiFlexGroup alignItems="center" gutterSize="none">
@ -37,13 +32,15 @@ export const RelatedAlertsUpsell = React.memo(() => {
</EuiFlexItem>
<EuiFlexItem>
<EuiText size="s">
<SubscriptionLink
<EuiLink
color="subdued"
target="_blank"
subscriptionContext={subscriptionContext}
href={application.getUrlForApp('management', {
path: 'stack/license_management/home',
})}
>
{INSIGHTS_UPSELL}
</SubscriptionLink>
</EuiLink>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -21,7 +21,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { Action } from '@kbn/ui-actions-plugin/public';
import { CellActionsProvider } from '@kbn/cell-actions';
import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout';
import { MockSubscriptionTrackingProvider } from '@kbn/subscription-tracking/mocks';
import { useKibana } from '../lib/kibana';
import { UpsellingProvider } from '../components/upselling_provider';
import { MockAssistantProvider } from './mock_assistant_provider';
@ -76,29 +75,27 @@ export const TestProvidersComponent: React.FC<Props> = ({
return (
<I18nProvider>
<MockKibanaContextProvider>
<MockSubscriptionTrackingProvider>
<UpsellingProviderMock>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<QueryClientProvider client={queryClient}>
<MockDiscoverInTimelineContext>
<MockAssistantProvider>
<ExpandableFlyoutProvider>
<ConsoleManager>
<CellActionsProvider
getTriggerCompatibleActions={() => Promise.resolve(cellActions)}
>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</CellActionsProvider>
</ConsoleManager>
</ExpandableFlyoutProvider>
</MockAssistantProvider>
</MockDiscoverInTimelineContext>
</QueryClientProvider>
</ThemeProvider>
</ReduxStoreProvider>
</UpsellingProviderMock>
</MockSubscriptionTrackingProvider>
<UpsellingProviderMock>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<QueryClientProvider client={queryClient}>
<MockDiscoverInTimelineContext>
<MockAssistantProvider>
<ExpandableFlyoutProvider>
<ConsoleManager>
<CellActionsProvider
getTriggerCompatibleActions={() => Promise.resolve(cellActions)}
>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</CellActionsProvider>
</ConsoleManager>
</ExpandableFlyoutProvider>
</MockAssistantProvider>
</MockDiscoverInTimelineContext>
</QueryClientProvider>
</ThemeProvider>
</ReduxStoreProvider>
</UpsellingProviderMock>
</MockKibanaContextProvider>
</I18nProvider>
);
@ -130,33 +127,31 @@ const TestProvidersWithPrivilegesComponent: React.FC<Props> = ({
return (
<I18nProvider>
<MockKibanaContextProvider>
<MockSubscriptionTrackingProvider>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<QueryClientProvider client={queryClient}>
<MockDiscoverInTimelineContext>
<MockAssistantProvider>
<UserPrivilegesProvider
kibanaCapabilities={
{
siem: { show: true, crud: true },
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: false },
[ASSISTANT_FEATURE_ID]: { 'ai-assistant': true },
} as unknown as Capabilities
}
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<QueryClientProvider client={queryClient}>
<MockDiscoverInTimelineContext>
<MockAssistantProvider>
<UserPrivilegesProvider
kibanaCapabilities={
{
siem: { show: true, crud: true },
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: false },
[ASSISTANT_FEATURE_ID]: { 'ai-assistant': true },
} as unknown as Capabilities
}
>
<CellActionsProvider
getTriggerCompatibleActions={() => Promise.resolve(cellActions)}
>
<CellActionsProvider
getTriggerCompatibleActions={() => Promise.resolve(cellActions)}
>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</CellActionsProvider>
</UserPrivilegesProvider>
</MockAssistantProvider>
</MockDiscoverInTimelineContext>
</QueryClientProvider>
</ThemeProvider>
</ReduxStoreProvider>
</MockSubscriptionTrackingProvider>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</CellActionsProvider>
</UserPrivilegesProvider>
</MockAssistantProvider>
</MockDiscoverInTimelineContext>
</QueryClientProvider>
</ThemeProvider>
</ReduxStoreProvider>
</MockKibanaContextProvider>
</I18nProvider>
);

View file

@ -19,7 +19,8 @@ import { RESPONSE_ACTION_TYPES } from '../../../../../common/api/detection_engin
import { login, ROLE } from '../../tasks/login';
describe('Form', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => {
describe('User with no access can not create an endpoint response action', () => {
// FLAKY: https://github.com/elastic/kibana/issues/169334
describe.skip('User with no access can not create an endpoint response action', () => {
before(() => {
login(ROLE.endpoint_response_actions_no_access);
});

View file

@ -226,11 +226,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
const services = await startServices(params);
await this.registerActions(store, params.history, services);
const subscriptionTrackingServices = {
analyticsClient: coreStart.analytics,
navigateToApp: coreStart.application.navigateToApp,
};
const { renderApp } = await this.lazyApplicationDependencies();
const { getSubPluginRoutesByCapabilities } = await this.lazyHelpersForRoutes();
@ -244,7 +239,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
coreStart.application.capabilities,
services
),
subscriptionTrackingServices,
});
},
});

View file

@ -173,10 +173,9 @@
"@kbn/security-solution-features",
"@kbn/content-management-plugin",
"@kbn/discover-utils",
"@kbn/subscription-tracking",
"@kbn/core-application-common",
"@kbn/openapi-generator",
"@kbn/es",
"@kbn/react-kibana-mount"
"@kbn/react-kibana-mount",
]
}

View file

@ -6,17 +6,21 @@
*/
import React, { VFC } from 'react';
import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
import {
EuiButton,
EuiButtonEmpty,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { SubscriptionButtonEmpty } from '@kbn/subscription-tracking';
import type { SubscriptionContextData } from '@kbn/subscription-tracking';
const subscriptionContext: SubscriptionContextData = {
feature: 'threat-intelligence',
source: 'security__threat-intelligence',
};
import { useKibana } from '../hooks/use_kibana';
export const Paywall: VFC = () => {
const {
services: { application },
} = useKibana();
return (
<EuiEmptyPrompt
icon={<EuiIcon type="logoSecurity" size="xl" />}
@ -52,12 +56,18 @@ export const Paywall: VFC = () => {
</EuiFlexItem>
<EuiFlexItem>
<div>
<SubscriptionButtonEmpty subscriptionContext={subscriptionContext}>
<EuiButtonEmpty
onClick={() =>
application.navigateToApp('management', {
path: 'stack/license_management/home',
})
}
>
<FormattedMessage
id="xpack.threatIntelligence.paywall.trial"
defaultMessage="Start a free trial"
/>
</SubscriptionButtonEmpty>
</EuiButtonEmpty>
</div>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -12,7 +12,6 @@ import { CoreStart, IUiSettingsClient } from '@kbn/core/public';
import { TimelinesUIStart } from '@kbn/timelines-plugin/public';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { RequestAdapter } from '@kbn/inspector-plugin/common';
import { MockSubscriptionTrackingProvider } from '@kbn/subscription-tracking/mocks';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { SettingsStart } from '@kbn/core-ui-settings-browser';
import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context';
@ -108,9 +107,7 @@ export const StoryProvidersComponent: VFC<StoryProvidersComponentProps> = ({
<SecuritySolutionContext.Provider value={securitySolutionContextMock}>
<IndicatorsFiltersContext.Provider value={mockIndicatorsFiltersContext}>
<KibanaReactContext.Provider>
<MockSubscriptionTrackingProvider>
<BlockListProvider>{children}</BlockListProvider>
</MockSubscriptionTrackingProvider>
<BlockListProvider>{children}</BlockListProvider>
</KibanaReactContext.Provider>
</IndicatorsFiltersContext.Provider>
</SecuritySolutionContext.Provider>

View file

@ -17,7 +17,6 @@ import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks
import { createTGridMocks } from '@kbn/timelines-plugin/public/mock';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { RequestAdapter } from '@kbn/inspector-plugin/common';
import { MockSubscriptionTrackingProvider } from '@kbn/subscription-tracking/mocks';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MemoryRouter } from 'react-router-dom';
import { casesPluginMock } from '@kbn/cases-plugin/public/mocks';
@ -142,13 +141,11 @@ export const TestProvidersComponent: FC = ({ children }) => (
<EuiThemeProvider>
<SecuritySolutionContext.Provider value={mockSecurityContext}>
<KibanaContext.Provider value={{ services: mockedServices } as any}>
<MockSubscriptionTrackingProvider>
<I18nProvider>
<IndicatorsFiltersContext.Provider value={mockIndicatorsFiltersContext}>
{children}
</IndicatorsFiltersContext.Provider>
</I18nProvider>
</MockSubscriptionTrackingProvider>
<I18nProvider>
<IndicatorsFiltersContext.Provider value={mockIndicatorsFiltersContext}>
{children}
</IndicatorsFiltersContext.Provider>
</I18nProvider>
</KibanaContext.Provider>
</SecuritySolutionContext.Provider>
</EuiThemeProvider>

View file

@ -11,7 +11,6 @@ import { Provider as ReduxStoreProvider } from 'react-redux';
import React, { Suspense } from 'react';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { ExternalReferenceAttachmentType } from '@kbn/cases-plugin/public/client/attachment_framework/types';
import { SubscriptionTrackingProvider } from '@kbn/subscription-tracking';
import { generateAttachmentType } from './modules/cases/utils/attachments';
import { KibanaContextProvider } from './hooks/use_kibana';
import {
@ -44,16 +43,11 @@ export const createApp =
<ReduxStoreProvider store={securitySolutionContext.securitySolutionStore}>
<SecuritySolutionContext.Provider value={securitySolutionContext}>
<KibanaContextProvider services={services}>
<SubscriptionTrackingProvider
analyticsClient={services.analytics}
navigateToApp={services.application.navigateToApp}
>
<EnterpriseGuard>
<Suspense fallback={<div />}>
<LazyIndicatorsPageWrapper />
</Suspense>
</EnterpriseGuard>
</SubscriptionTrackingProvider>
<EnterpriseGuard>
<Suspense fallback={<div />}>
<LazyIndicatorsPageWrapper />
</Suspense>
</EnterpriseGuard>
</KibanaContextProvider>
</SecuritySolutionContext.Provider>
</ReduxStoreProvider>

View file

@ -32,8 +32,7 @@
"@kbn/utility-types",
"@kbn/ui-theme",
"@kbn/securitysolution-io-ts-list-types",
"@kbn/core-ui-settings-browser",
"@kbn/subscription-tracking"
"@kbn/core-ui-settings-browser"
],
"exclude": ["target/**/*"]
}

View file

@ -5951,10 +5951,6 @@
version "0.0.0"
uid ""
"@kbn/subscription-tracking@link:packages/kbn-subscription-tracking":
version "0.0.0"
uid ""
"@kbn/synthetics-plugin@link:x-pack/plugins/synthetics":
version "0.0.0"
uid ""