[Cloud Security] Adding MSW for mocking server responses in React integration tests (#184555)

## Summary

It closes #183977

This PR introduces the MSW library into Kibana and setups for MSW usage
with Jest for integration testing of React components in the Cloud
Security Posture plugin.

It also adds the setup for the initial
[handlers](https://mswjs.io/docs/concepts/request-handler/), and
configures a test for the `<NoFindingsStates/>` components using MSW to
exemplify how the library works.

### Problem Statement

Currently, integration tests for React components that interact with the
server are hard to write and maintain, as they often require mocking
functions implementation and responses, this can lead to tests that do
not accurately verify the intended functionality and can be hard to
maintain as the implementation of the functions changes.

This leads to situations our team faces now, where due to the difficult
maintainability of integration tests, we rely much more on End-to-End
tests, and maintaining those many End-to-End comes with its own set of
tradeoffs, as oftentimes End-to-End tests are detected by the CI as
failing or flaky, and as flakiness can happen in End-to-end tests due to
its nature of multiple integrated systems, this concept proposes that
it's better to reserve End-to-end tests for the features in the most
critical path and tests that test multiple integrated systems as those
will benefit most of the end-to-end testing. For all the other tests we
should focus on unit and integration tests.

### How MSW works

MSW is a library that allows you to mock server responses in your tests,
it works by intercepting the requests made by the client and returning
the mocked responses, this way we can test how the client behaves in
different states of the lifecycle such as loading, error, and success.

This proposes that we should use MSW to enhance our integration tests,
and give preference to writing integration tests over End-to-End tests
whenever possible, but this doesn't mean that we should stop writing
end-to-end tests, as end-to-end tests are still important for the
features in the most critical path and tests that tests multiple
integrated systems.


### MSW Diagram

Here's a diagram that shows how MSW works with Jest tests:

```mermaid
%%{init:{'themeCSS':' g:nth-of-type(3) rect.actor { fill: #eee; };g:nth-of-type(7) rect.actor { fill: #eee; };'}}%%

sequenceDiagram
    participant ReactComponent as React Component
    participant API as API
    participant MSW as MSW Mock Server
    participant Jest as Jest Test

    Jest->>ReactComponent: Setup component test and mock providers
    Jest->>MSW: Setup Mock Server
    Note over Jest,MSW: start listening for requests
    activate MSW
    ReactComponent->>API: Make API Call
    Note over ReactComponent,API: loading state
    activate API
    MSW-->>API: Intercepts API Call
    deactivate API
    alt is success
        MSW-->>ReactComponent: Send Mocked success Response
    else is error
        MSW-->>ReactComponent: Send Mocked error Response
    end
    deactivate MSW
    ReactComponent-->>Jest: Receive Mocked data and render
```


### Documentation

- Refer to this [PR](https://github.com/elastic/security-team/pull/9624)
containing the documentation of how MSW works and how to use it.
- Refer to this
[presentation](https://docs.google.com/presentation/d/1KYtBaeDMZrpoU5lnKASm8GvCxhrXVqMKxWgR-Xvaxzc/edit#slide=id.g11f0f180654_1_0)
to understand the main motivations behind this proposal.

### How to test it


```
yarn test:jest x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx

```


### Screenshot


![image](f0673be2-f087-42b5-8ed6-42ce3159e378)


Intercepted requests logged with `{debug: true}`:


![image](b512d486-8a2a-422e-bf26-3c6b60a8c6d2)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Paulo Henrique 2024-06-18 11:08:01 -07:00 committed by GitHub
parent e2a98cf965
commit cb11a1b004
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1182 additions and 346 deletions

View file

@ -1675,6 +1675,7 @@
"mochawesome-merge": "^4.3.0",
"mock-fs": "^5.1.2",
"ms-chromium-edge-driver": "^0.5.1",
"msw": "^2.3.1",
"multistream": "^4.1.0",
"mutation-observer": "^1.0.3",
"native-hdr-histogram": "^1.0.0",

View file

@ -427,6 +427,14 @@
"prCreation": "not-pending",
"minimumReleaseAge": "7 days",
"enabled": true
},
{
"groupName": "MSW",
"matchPackageNames": ["msw"],
"reviewers": ["team:kibana-cloud-security-posture"],
"matchBaseBranches": ["main"],
"labels": ["Team: Cloud Security", "release_note:skip", "backport:skip"],
"enabled": true
}
]
}

View file

@ -15,4 +15,13 @@ module.exports = {
collectCoverageFrom: [
'<rootDir>/x-pack/plugins/cloud_security_posture/{common,public,server}/**/*.{ts,tsx}',
],
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
transformIgnorePatterns: [
// ignore all node_modules except the modules below (monaco-editor, monaco-yaml, react-monaco-editor, etc) which requires babel transforms to handle dynamic import()
// since ESM modules are not natively supported in Jest yet (https://github.com/facebook/jest/issues/4842)
'[/\\\\]node_modules(?![\\/\\\\](byte-size|monaco-editor|monaco-yaml|monaco-languageserver-types|monaco-marker-data-provider|monaco-worker-manager|vscode-languageserver-types|react-monaco-editor|d3-interpolate|d3-color|langchain|langsmith|@cfworker|gpt-tokenizer|flat|@langchain|msw|@bundled-es-modules))[/\\\\].+\\.js$',
'packages/kbn-pm/dist/index.js',
'[/\\\\]node_modules(?![\\/\\\\](langchain|langsmith|@langchain))/dist/[/\\\\].+\\.js$',
'[/\\\\]node_modules(?![\\/\\\\](langchain|langsmith|@langchain))/dist/util/[/\\\\].+\\.js$',
],
};

View file

@ -5,23 +5,21 @@
* 2.0.
*/
import { useQuery } from '@tanstack/react-query';
import { useKibana } from '../hooks/use_kibana';
const LICENSE_MANAGEMENT_LOCATOR = 'LICENSE_MANAGEMENT_LOCATOR';
const getLicenseManagementLocatorKey = 'license_management_url_key';
/**
* Hook to get the license management locator
* @returns a callback to navigate to the license management page
*/
export const useLicenseManagementLocatorApi = () => {
const { share } = useKibana().services;
return useQuery([getLicenseManagementLocatorKey], () => {
const locator = share.url.locators.get(LICENSE_MANAGEMENT_LOCATOR);
// license management does not exist on serverless
if (!locator) return;
const locator = share.url.locators.get(LICENSE_MANAGEMENT_LOCATOR);
return locator.getUrl({
page: 'dashboard',
});
});
// license management does not exist on serverless
if (!locator) return;
return () => locator.navigate({ page: 'dashboard' });
};

View file

@ -43,12 +43,7 @@ describe('<CloudPosturePage />', () => {
})
);
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: true,
})
);
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(undefined);
});
const renderCloudPosturePage = (
@ -85,7 +80,10 @@ describe('<CloudPosturePage />', () => {
})
);
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() => 'http://license-url');
renderCloudPosturePage();
expect(screen.getByTestId('has_locator')).toBeInTheDocument();
});
@ -97,12 +95,7 @@ describe('<CloudPosturePage />', () => {
})
);
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: undefined,
})
);
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(undefined);
renderCloudPosturePage();
expect(screen.getByTestId('no_locator')).toBeInTheDocument();

View file

@ -15,7 +15,6 @@ import { SubscriptionNotAllowed } from './subscription_not_allowed';
import { useSubscriptionStatus } from '../common/hooks/use_subscription_status';
import { FullSizeCenteredPage } from './full_size_centered_page';
import { CspLoadingState } from './csp_loading_state';
import { useLicenseManagementLocatorApi } from '../common/api/use_license_management_locator_api';
export const LOADING_STATE_TEST_SUBJECT = 'cloud_posture_page_loading';
export const ERROR_STATE_TEST_SUBJECT = 'cloud_posture_page_error';
@ -151,9 +150,9 @@ export const defaultNoDataRenderer = () => (
</FullSizeCenteredPage>
);
const subscriptionNotAllowedRenderer = (licenseManagementLocator?: string) => (
const subscriptionNotAllowedRenderer = () => (
<FullSizeCenteredPage data-test-subj={SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT}>
<SubscriptionNotAllowed licenseManagementLocator={licenseManagementLocator} />
<SubscriptionNotAllowed />
</FullSizeCenteredPage>
);
@ -173,19 +172,18 @@ export const CloudPosturePage = <TData, TError>({
noDataRenderer = defaultNoDataRenderer,
}: CloudPosturePageProps<TData, TError>) => {
const subscriptionStatus = useSubscriptionStatus();
const getLicenseManagementLocator = useLicenseManagementLocatorApi();
const render = () => {
if (subscriptionStatus.isError) {
return defaultErrorRenderer(subscriptionStatus.error);
}
if (subscriptionStatus.isLoading || getLicenseManagementLocator.isLoading) {
if (subscriptionStatus.isLoading) {
return defaultLoadingRenderer();
}
if (!subscriptionStatus.data) {
return subscriptionNotAllowedRenderer(getLicenseManagementLocator.data);
return subscriptionNotAllowedRenderer();
}
if (!query) {

View file

@ -1,245 +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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { render } from '@testing-library/react';
import { createReactQueryResponse } from '../test/fixtures/react_query';
import { TestProvider } from '../test/test_provider';
import { NoFindingsStates } from './no_findings_states';
import { useCspSetupStatusApi } from '../common/api/use_setup_status_api';
import { useCspIntegrationLink } from '../common/navigation/use_csp_integration_link';
import { useLicenseManagementLocatorApi } from '../common/api/use_license_management_locator_api';
import { useSubscriptionStatus } from '../common/hooks/use_subscription_status';
jest.mock('../common/api/use_setup_status_api', () => ({
useCspSetupStatusApi: jest.fn(),
}));
jest.mock('../common/navigation/use_csp_integration_link', () => ({
useCspIntegrationLink: jest.fn(),
}));
jest.mock('../common/api/use_license_management_locator_api', () => ({
useLicenseManagementLocatorApi: jest.fn(),
}));
jest.mock('../common/hooks/use_subscription_status', () => ({
useSubscriptionStatus: jest.fn(),
}));
const customRederer = (postureType: 'cspm' | 'kspm') => {
return render(
<TestProvider>
<NoFindingsStates postureType={postureType} />
</TestProvider>
);
};
describe('NoFindingsStates', () => {
beforeEach(() => {
jest.clearAllMocks();
(useSubscriptionStatus as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: true,
})
);
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: true,
})
);
(useCspIntegrationLink as jest.Mock).mockReturnValue('http://cspm-or-kspm-integration-link');
});
it('should show the indexing notification when CSPM is not installed and KSPM is indexing', async () => {
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: {
cspm: {
status: 'not-deployed',
},
kspm: {
status: 'indexing',
},
indicesDetails: [
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
],
},
})
);
const { getByText } = customRederer('kspm');
expect(getByText(/posture evaluation underway/i)).toBeInTheDocument();
expect(
getByText(
/waiting for data to be collected and indexed. check back later to see your findings/i
)
).toBeInTheDocument();
});
it('should show the indexing notification when KSPM is not installed and CSPM is indexing', async () => {
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: {
kspm: {
status: 'not-deployed',
},
cspm: {
status: 'indexing',
},
indicesDetails: [
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
],
},
})
);
const { getByText } = customRederer('cspm');
expect(getByText(/posture evaluation underway/i)).toBeInTheDocument();
expect(
getByText(
/waiting for data to be collected and indexed. Check back later to see your findings/i
)
).toBeInTheDocument();
});
it('should show the indexing timout notification when CSPM is status is index-timeout', async () => {
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: {
kspm: {
status: 'installed',
},
cspm: {
status: 'index-timeout',
},
indicesDetails: [
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
],
},
})
);
const { getByText } = customRederer('cspm');
expect(getByText(/waiting for findings data/i)).toBeInTheDocument();
const indexTimeOutMessage = getByText(/collecting findings is taking longer than expected/i);
expect(indexTimeOutMessage).toBeInTheDocument();
});
it('should show the indexing timout notification when KSPM is status is index-timeout', async () => {
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: {
kspm: {
status: 'index-timeout',
},
cspm: {
status: 'installed',
},
indicesDetails: [
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
],
},
})
);
const { getByText } = customRederer('kspm');
expect(getByText(/waiting for findings data/i)).toBeInTheDocument();
expect(getByText(/collecting findings is taking longer than expected/i)).toBeInTheDocument();
});
it('should show the unprivileged notification when CSPM is status is index-timeout', async () => {
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: {
kspm: {
status: 'installed',
},
cspm: {
status: 'unprivileged',
},
indicesDetails: [
{
index: 'logs-cloud_security_posture.findings_latest-default',
status: 'unprivileged',
},
{ index: 'logs-cloud_security_posture.findings-default*', status: 'unprivileged' },
],
},
})
);
const { getByText } = customRederer('cspm');
expect(getByText(/privileges required/i)).toBeInTheDocument();
});
it('should show the unprivileged notification when KSPM is status is index-timeout', async () => {
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: {
kspm: {
status: 'unprivileged',
},
cspm: {
status: 'installed',
},
indicesDetails: [
{
index: 'logs-cloud_security_posture.findings_latest-default',
status: 'unprivileged',
},
{ index: 'logs-cloud_security_posture.findings-default*', status: 'unprivileged' },
],
},
})
);
const { getByText } = customRederer('kspm');
expect(getByText(/privileges required/i)).toBeInTheDocument();
});
it('should show the not-installed notification when CSPM and KSPM status is not-installed', async () => {
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: {
kspm: {
status: 'not-installed',
},
cspm: {
status: 'not-installed',
},
indicesDetails: [
{
index: 'logs-cloud_security_posture.findings_latest-default',
status: 'success',
},
{ index: 'logs-cloud_security_posture.findings-default*', status: 'success' },
],
},
})
);
const { getByText } = customRederer('cspm');
expect(getByText(/learn more about cloud security posture/i)).toBeInTheDocument();
});
});

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export * from './no_findings_states';

View file

@ -0,0 +1,20 @@
/*
* 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 { http, HttpResponse } from 'msw';
export const fleetCspPackageHandler = http.get(
`/api/fleet/epm/packages/cloud_security_posture`,
() => {
return HttpResponse.json({
item: {
name: 'cloud_security_posture',
version: '1.9.0',
},
});
}
);

View file

@ -0,0 +1,163 @@
/*
* 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 from 'react';
import { screen, waitFor } from '@testing-library/react';
import { setupMockServer, startMockServer } from '../../test/mock_server/mock_server';
import { renderWrapper } from '../../test/mock_server/mock_server_test_provider';
import { NoFindingsStates } from './no_findings_states';
import * as statusHandlers from '../../../server/routes/status/status.handlers.mock';
import * as benchmarksHandlers from '../../../server/routes/benchmarks/benchmarks.handlers.mock';
import { fleetCspPackageHandler } from './no_findings_states.handlers.mock';
const server = setupMockServer();
const renderNoFindingsStates = (postureType: 'cspm' | 'kspm' = 'cspm') => {
return renderWrapper(<NoFindingsStates postureType={postureType} />);
};
describe('NoFindingsStates', () => {
startMockServer(server);
beforeEach(() => {
server.use(fleetCspPackageHandler);
});
it('shows integrations installation prompt with installation links when integration is not-installed', async () => {
server.use(statusHandlers.notInstalledHandler);
renderNoFindingsStates();
expect(screen.getByText(/loading/i)).toBeInTheDocument();
await waitFor(() => {
expect(
screen.getByText(/detect security misconfigurations in your cloud infrastructure!/i)
).toBeInTheDocument();
});
await waitFor(() => {
expect(screen.getByRole('link', { name: /add cspm integration/i })).toHaveAttribute(
'href',
'/app/fleet/integrations/cloud_security_posture-1.9.0/add-integration/cspm'
);
});
await waitFor(() => {
expect(screen.getByRole('link', { name: /add kspm integration/i })).toHaveAttribute(
'href',
'/app/fleet/integrations/cloud_security_posture-1.9.0/add-integration/kspm'
);
});
});
it('shows install agent prompt with install agent link when status is not-deployed', async () => {
server.use(statusHandlers.notDeployedHandler);
server.use(benchmarksHandlers.cspmInstalledHandler);
renderNoFindingsStates();
expect(screen.getByText(/loading/i)).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText(/no agents installed/i)).toBeInTheDocument();
});
await waitFor(() => {
expect(screen.getByRole('link', { name: /install agent/i })).toHaveAttribute(
'href',
'/app/integrations/detail/cloud_security_posture-1.9.0/policies?addAgentToPolicyId=30cba674-531c-4225-b392-3f7810957511&integration=630f3e42-659e-4499-9007-61e36adf1d97'
);
});
});
it('shows install agent prompt with install agent link when status is not-deployed and postureType is KSPM', async () => {
server.use(statusHandlers.notDeployedHandler);
server.use(benchmarksHandlers.kspmInstalledHandler);
renderNoFindingsStates('kspm');
expect(screen.getByText(/loading/i)).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText(/no agents installed/i)).toBeInTheDocument();
});
await waitFor(() => {
const link = screen.getByRole('link', {
name: /install agent/i,
});
expect(link).toHaveAttribute(
'href',
'/app/integrations/detail/cloud_security_posture-1.9.0/policies?addAgentToPolicyId=e2f72eea-bf76-4576-bed8-e29d2df102a7&integration=6aedf856-bc21-49aa-859a-a0952789f898'
);
});
});
it('shows indexing message when status is indexing', async () => {
server.use(statusHandlers.indexingHandler);
renderNoFindingsStates();
expect(screen.getByText(/loading/i)).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText(/posture evaluation underway/i)).toBeInTheDocument();
});
expect(
screen.getByText(
/waiting for data to be collected and indexed. check back later to see your findings/i
)
).toBeInTheDocument();
});
it('shows timeout message when status is index-timeout', async () => {
server.use(statusHandlers.indexTimeoutHandler);
renderNoFindingsStates();
expect(screen.getByText(/loading/i)).toBeInTheDocument();
await waitFor(() => {
screen.getByRole('heading', {
name: /waiting for findings data/i,
});
});
expect(
screen.getByText(/collecting findings is taking longer than expected/i)
).toBeInTheDocument();
});
it('shows unprivileged message when status is unprivileged', async () => {
server.use(statusHandlers.unprivilegedHandler);
renderNoFindingsStates();
expect(screen.getByText(/loading/i)).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText(/privileges required/i)).toBeInTheDocument();
expect(
screen.getByText(/required elasticsearch index privilege for the following indices:/i)
).toBeInTheDocument();
expect(
screen.getByText('logs-cloud_security_posture.findings_latest-default')
).toBeInTheDocument();
expect(screen.getByText('logs-cloud_security_posture.findings-default*')).toBeInTheDocument();
expect(screen.getByText('logs-cloud_security_posture.scores-default')).toBeInTheDocument();
expect(
screen.getByText('logs-cloud_security_posture.vulnerabilities_latest-default')
).toBeInTheDocument();
});
});
it('renders empty container when the status does not match a no finding status', async () => {
server.use(statusHandlers.indexedHandler);
const { container } = renderNoFindingsStates();
expect(screen.getByText(/loading/i)).toBeInTheDocument();
await waitFor(() => {
expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
});
expect(container).toMatchInlineSnapshot(`
<div>
<div
class="euiFlexGroup emotion-euiFlexGroup-l-center-center-column"
/>
</div>
`);
});
});

View file

@ -20,21 +20,21 @@ import {
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/react';
import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../common/constants';
import { FullSizeCenteredPage } from './full_size_centered_page';
import { useCISIntegrationPoliciesLink } from '../common/navigation/use_navigate_to_cis_integration_policies';
import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../../common/constants';
import { FullSizeCenteredPage } from '../full_size_centered_page';
import { useCISIntegrationPoliciesLink } from '../../common/navigation/use_navigate_to_cis_integration_policies';
import {
CSPM_NOT_INSTALLED_ACTION_SUBJ,
KSPM_NOT_INSTALLED_ACTION_SUBJ,
NO_FINDINGS_STATUS_TEST_SUBJ,
} from './test_subjects';
import { CloudPosturePage, PACKAGE_NOT_INSTALLED_TEST_SUBJECT } from './cloud_posture_page';
import { useCspSetupStatusApi } from '../common/api/use_setup_status_api';
import type { IndexDetails, PostureTypes, CspStatusCode } from '../../common/types_old';
import noDataIllustration from '../assets/illustrations/no_data_illustration.svg';
import { useCspIntegrationLink } from '../common/navigation/use_csp_integration_link';
import { NO_FINDINGS_STATUS_REFRESH_INTERVAL_MS } from '../common/constants';
import { cspIntegrationDocsNavigation } from '../common/navigation/constants';
} from '../test_subjects';
import { CloudPosturePage, PACKAGE_NOT_INSTALLED_TEST_SUBJECT } from '../cloud_posture_page';
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
import type { IndexDetails, PostureTypes, CspStatusCode } from '../../../common/types_old';
import noDataIllustration from '../../assets/illustrations/no_data_illustration.svg';
import { useCspIntegrationLink } from '../../common/navigation/use_csp_integration_link';
import { NO_FINDINGS_STATUS_REFRESH_INTERVAL_MS } from '../../common/constants';
import { cspIntegrationDocsNavigation } from '../../common/navigation/constants';
const NotDeployed = ({ postureType }: { postureType: PostureTypes }) => {
const integrationPoliciesLink = useCISIntegrationPoliciesLink({
@ -169,13 +169,10 @@ const Unprivileged = ({ unprivilegedIndices }: { unprivilegedIndices: string[] }
/>
);
const EmptySecurityFindingsPrompt = ({
kspmIntegrationLink,
cspmIntegrationLink,
}: {
kspmIntegrationLink?: string;
cspmIntegrationLink?: string;
}) => {
const EmptySecurityFindingsPrompt = () => {
const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE);
const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE);
return (
<EuiEmptyPrompt
data-test-subj={PACKAGE_NOT_INSTALLED_TEST_SUBJECT}
@ -215,6 +212,7 @@ const EmptySecurityFindingsPrompt = ({
color="primary"
fill
href={cspmIntegrationLink}
isDisabled={!cspmIntegrationLink}
data-test-subj={CSPM_NOT_INSTALLED_ACTION_SUBJ}
>
<FormattedMessage
@ -228,6 +226,7 @@ const EmptySecurityFindingsPrompt = ({
color="primary"
fill
href={kspmIntegrationLink}
isDisabled={!kspmIntegrationLink}
data-test-subj={KSPM_NOT_INSTALLED_ACTION_SUBJ}
>
<FormattedMessage
@ -253,9 +252,6 @@ const NoFindingsStatesNotification = ({
indicesStatus?: IndexDetails[];
isNotInstalled: boolean;
}) => {
const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE);
const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE);
const unprivilegedIndices =
indicesStatus &&
indicesStatus
@ -267,13 +263,7 @@ const NoFindingsStatesNotification = ({
return <Unprivileged unprivilegedIndices={unprivilegedIndices || []} />;
if (status === 'indexing' || status === 'waiting_for_results') return <Indexing />;
if (status === 'index-timeout') return <IndexTimeout />;
if (isNotInstalled)
return (
<EmptySecurityFindingsPrompt
kspmIntegrationLink={kspmIntegrationLink}
cspmIntegrationLink={cspmIntegrationLink}
/>
);
if (isNotInstalled) return <EmptySecurityFindingsPrompt />;
if (status === 'not-deployed') return <NotDeployed postureType={postureType} />;
return null;

View file

@ -8,12 +8,11 @@
import React from 'react';
import { EuiEmptyPrompt, EuiLink, EuiPageSection } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useLicenseManagementLocatorApi } from '../common/api/use_license_management_locator_api';
export const SubscriptionNotAllowed = () => {
const handleNavigateToLicenseManagement = useLicenseManagementLocatorApi();
export const SubscriptionNotAllowed = ({
licenseManagementLocator,
}: {
licenseManagementLocator?: string;
}) => {
return (
<EuiPageSection color="danger" alignment="center">
<EuiEmptyPrompt
@ -27,14 +26,14 @@ export const SubscriptionNotAllowed = ({
</h2>
}
body={
licenseManagementLocator ? (
handleNavigateToLicenseManagement ? (
<p data-test-subj={'has_locator'}>
<FormattedMessage
id="xpack.csp.subscriptionNotAllowed.promptDescription"
defaultMessage="To use these cloud security features, you must {link}."
values={{
link: (
<EuiLink href={licenseManagementLocator}>
<EuiLink onClick={handleNavigateToLicenseManagement}>
<FormattedMessage
id="xpack.csp.subscriptionNotAllowed.promptLinkText"
defaultMessage="start a trial or upgrade your subscription"

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks';
import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
import { sessionStorageMock } from '@kbn/core-http-server-mocks';
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
export const getMockDependencies = () => ({
data: dataPluginMock.createStartContract(),
unifiedSearch: unifiedSearchPluginMock.createStartContract(),
charts: chartPluginMock.createStartContract(),
discover: discoverPluginMock.createStartContract(),
fleet: fleetMock.createStartMock(),
licensing: licensingMock.createStart(),
uiActions: uiActionsPluginMock.createStartContract(),
storage: sessionStorageMock.create(),
share: sharePluginMock.createStartContract(),
});

View file

@ -0,0 +1,15 @@
/*
* 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 { defaultApiLicensingInfo } from './licensing.handlers.mock';
/**
* Default handlers for the mock server, these are the handlers that are always enabled
* when the mock server is started, but can be overridden by specific tests when needed.
* Recommended to use these handlers for common endpoints.
*/
export const defaultHandlers = [defaultApiLicensingInfo];

View file

@ -0,0 +1,32 @@
/*
* 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 { http, HttpResponse } from 'msw';
export const MOCK_SERVER_LICENSING_INFO_URL = `/api/licensing/info`;
export const defaultApiLicensingInfo = http.get(MOCK_SERVER_LICENSING_INFO_URL, () => {
const date = new Date();
const expiryDateInMillis = date.setDate(date.getDate() + 30);
return HttpResponse.json({
license: {
uid: '000000-0000-0000-0000-000000000',
type: 'trial',
mode: 'trial',
expiryDateInMillis,
status: 'active',
},
features: {
security: {
isAvailable: true,
isEnabled: true,
},
},
signature: '0000000000000000000000000000000000000000000000000000000',
});
});

View file

@ -0,0 +1,174 @@
/*
* 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 { setupServer, SetupServerApi } from 'msw/node';
import { coreMock } from '@kbn/core/public/mocks';
import type { CoreStart } from '@kbn/core/public';
import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock';
import { createStubDataView } from '@kbn/data-views-plugin/common/stubs';
import { indexPatternFieldEditorPluginMock as dataViewFieldEditorMock } from '@kbn/data-view-field-editor-plugin/public/mocks';
import SearchBar from '@kbn/unified-search-plugin/public/search_bar/search_bar';
import { http, HttpResponse, JsonBodyType } from 'msw';
import { defaultHandlers } from './handlers';
import { getMockDependencies } from '../fixtures/get_mock_dependencies';
import { CspClientPluginStartDeps } from '../../types';
import { MOCK_SERVER_LICENSING_INFO_URL } from './handlers/licensing.handlers.mock';
/**
* Mock the lastValueFrom function from rxjs to return the result of the promise instead of the Observable
* This is for simplifying the testing by avoiding the need to subscribe to the Observable while producing the same result
*/
jest.mock('rxjs', () => {
const actual = jest.requireActual('rxjs');
return {
...actual,
lastValueFrom: async (source: Promise<any>) => {
const value = await source;
return value.result;
},
};
});
/**
* Setup a mock server with the default handlers
* @param debug - If true, log all requests to the console
* @returns The mock server
*/
export const setupMockServer = ({ debug = false }: { debug?: boolean } = {}) => {
const server = setupServer(...defaultHandlers);
if (debug) {
// Debug: log all requests to the console
server.events.on('request:start', async ({ request }) => {
const payload = await request.clone().text();
// eslint-disable-next-line no-console
console.log('MSW intercepted request:', request.method, request.url, payload);
});
server.events.on('response:mocked', async ({ request, response }) => {
const body = await response.json();
// eslint-disable-next-line no-console
console.log(
'%s %s received %s %s %s',
request.method,
request.url,
response.status,
response.statusText,
JSON.stringify(body, null, 2)
);
});
}
return server;
};
/**
* This function wraps beforeAll, afterAll and beforeEach for setup MSW server into a single call.
* That makes the describe code further down easier to read and makes
* sure we don't forget the handlers. Can easily be shared between tests.
* @param server - The MSW server instance, created with setupMockServer
*/
export const startMockServer = (server: SetupServerApi) => {
beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }));
afterAll(() => server.close());
beforeEach(() => {
server.resetHandlers();
});
};
const MOCK_SERVER_BASE_URL = 'http://localhost';
/**
* Get a set of dependencies for the mock server overriding default mock dependencies to perform
* HTTP calls that will be intercepted by the mock server
* @returns The core and deps dependencies used by the KibanaContextProvider
*/
export const getMockServerDependencies = () => {
return {
deps: {
...getMockDependencies(),
data: {
...getMockDependencies().data,
search: {
...getMockDependencies().data.search,
search: async ({ params }: { params: any }) => {
const response = await fetch(`${MOCK_SERVER_BASE_URL}/internal/bsearch`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
return response.json();
},
},
dataViews: {
...getMockDependencies().data.dataViews,
find: async (pattern: string) => {
const response = await fetch(
`${MOCK_SERVER_BASE_URL}/internal/data_views/fields?pattern=${pattern}`
);
const responseJson = await response.json();
const fields = responseJson.fields.reduce((acc: any, field: any) => {
acc[field.name] = field;
return acc;
}, {});
const dataView = createStubDataView({
spec: {
id: pattern,
title: pattern,
fields,
},
});
return [dataView];
},
},
},
licensing: {
...getMockDependencies().licensing,
refresh: async () => {
const response = await fetch(MOCK_SERVER_LICENSING_INFO_URL);
const responseJson = await response.json();
return licenseMock.createLicense(responseJson);
},
},
dataViewFieldEditor: dataViewFieldEditorMock.createStartContract(),
unifiedSearch: {
...getMockDependencies().unifiedSearch,
ui: {
...getMockDependencies().unifiedSearch.ui,
SearchBar,
},
},
storage: {
...getMockDependencies().storage,
get: (key: string) => {
return localStorage.getItem(key);
},
set: (key: string, value: string) => {
localStorage.setItem(key, value);
},
},
} as unknown as Partial<CspClientPluginStartDeps>,
core: {
...coreMock.createStart(),
http: {
...coreMock.createStart().http,
get: async (path: string, options: any) => {
const response = await fetch(`${MOCK_SERVER_BASE_URL}${path}`, options);
return response.json();
},
},
} as unknown as CoreStart,
};
};
export const mockGetRequest = (path: string, response: JsonBodyType) => {
return http.get(path, () => HttpResponse.json(response));
};

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 from 'react';
import { render } from '@testing-library/react';
import type { CoreStart } from '@kbn/core/public';
import { CspClientPluginStartDeps } from '../../types';
import { TestProvider } from '../test_provider';
import { getMockServerDependencies } from './mock_server';
interface MockServerDependencies {
deps: Partial<CspClientPluginStartDeps>;
core: CoreStart;
}
interface MockServerTestProviderProps {
children: React.ReactNode;
dependencies?: MockServerDependencies;
}
/**
* Simple wrapper around the TestProvider that provides dependencies for the mock server.
*/
export const MockServerTestProvider = ({
children,
dependencies = getMockServerDependencies(),
}: MockServerTestProviderProps) => {
return <TestProvider {...dependencies}>{children}</TestProvider>;
};
/**
* Renders a component wrapped in the MockServerTestProvider.
*/
export const renderWrapper = (children: React.ReactNode, dependencies?: MockServerDependencies) => {
return render(
<MockServerTestProvider dependencies={dependencies}>{children}</MockServerTestProvider>
);
};

View file

@ -6,23 +6,16 @@
*/
import type { AppMountParameters, CoreStart } from '@kbn/core/public';
import React, { useMemo } from 'react';
import React from 'react';
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 { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { coreMock } from '@kbn/core/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks';
import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
import { sessionStorageMock } from '@kbn/core-http-server-mocks';
import type { CspClientPluginStartDeps } from '../types';
import { getMockDependencies } from './fixtures/get_mock_dependencies';
interface CspAppDeps {
core: CoreStart;
@ -33,20 +26,17 @@ interface CspAppDeps {
export const TestProvider: React.FC<Partial<CspAppDeps>> = ({
core = coreMock.createStart(),
deps = {
data: dataPluginMock.createStartContract(),
unifiedSearch: unifiedSearchPluginMock.createStartContract(),
charts: chartPluginMock.createStartContract(),
discover: discoverPluginMock.createStartContract(),
fleet: fleetMock.createStartMock(),
licensing: licensingMock.createStart(),
uiActions: uiActionsPluginMock.createStartContract(),
storage: sessionStorageMock.create(),
},
deps = getMockDependencies(),
params = coreMock.createAppMountParameters(),
children,
} = {}) => {
const queryClient = useMemo(() => new QueryClient(), []);
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
return (
<KibanaContextProvider services={{ ...core, ...deps }}>

View file

@ -0,0 +1,192 @@
/*
* 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 { http, HttpResponse } from 'msw';
export const cspmInstalledHandler = http.get('/internal/cloud_security_posture/benchmarks', () => {
return HttpResponse.json({
items: [
{
package_policy: {
id: '630f3e42-659e-4499-9007-61e36adf1d97',
name: 'cspm-1',
namespace: 'default',
description: '',
package: {
name: 'cloud_security_posture',
title: 'Security Posture Management',
version: '1.9.0',
},
enabled: true,
policy_id: '30cba674-531c-4225-b392-3f7810957511',
inputs: [
{
type: 'cloudbeat/cis_aws',
policy_template: 'cspm',
enabled: true,
streams: [
{
enabled: true,
data_stream: {
type: 'logs',
dataset: 'cloud_security_posture.findings',
},
vars: {
access_key_id: {
type: 'text',
},
secret_access_key: {
type: 'text',
},
session_token: {
type: 'text',
},
shared_credential_file: {
type: 'text',
},
credential_profile_name: {
type: 'text',
},
role_arn: {
type: 'text',
},
'aws.credentials.type': {
type: 'text',
},
'aws.account_type': {
value: 'organization-account',
type: 'text',
},
},
id: 'cloudbeat/cis_aws-cloud_security_posture.findings-630f3e42-659e-4499-9007-61e36adf1d97',
compiled_stream: {
period: '24h',
config: {
v1: {
type: 'cspm',
deployment: 'aws',
benchmark: 'cis_aws',
aws: {
account_type: 'organization-account',
credentials: {
type: null,
},
},
},
},
},
},
],
config: {
cloud_formation_template_url: {
value:
'https://console.aws.amazon.com/cloudformation/home#/stacks/quickcreate?templateURL=https://elastic-cspm-cft.s3.eu-central-1.amazonaws.com/cloudformation-cspm-ACCOUNT_TYPE-8.14.0.yml&stackName=Elastic-Cloud-Security-Posture-Management&param_EnrollmentToken=FLEET_ENROLLMENT_TOKEN&param_FleetUrl=FLEET_URL&param_ElasticAgentVersion=KIBANA_VERSION&param_ElasticArtifactServer=https://artifacts.elastic.co/downloads/beats/elastic-agent',
},
},
},
],
vars: {
posture: {
value: 'cspm',
type: 'text',
},
deployment: {
value: 'aws',
type: 'text',
},
},
revision: 1,
created_at: '2024-06-03T21:06:20.786Z',
created_by: 'system',
updated_at: '2024-06-03T21:06:20.786Z',
updated_by: 'system',
},
agent_policy: {
id: '30cba674-531c-4225-b392-3f7810957511',
name: 'Agent policy 3',
agents: 0,
},
rules_count: 55,
},
],
total: 1,
page: 1,
perPage: 100,
});
});
export const kspmInstalledHandler = http.get('/internal/cloud_security_posture/benchmarks', () => {
return HttpResponse.json({
items: [
{
package_policy: {
id: '6aedf856-bc21-49aa-859a-a0952789f898',
version: 'WzE4ODcxLDE0XQ==',
name: 'kspm-1',
namespace: 'default',
description: '',
package: {
name: 'cloud_security_posture',
title: 'Security Posture Management',
version: '1.9.0',
},
enabled: true,
policy_id: 'e2f72eea-bf76-4576-bed8-e29d2df102a7',
inputs: [
{
type: 'cloudbeat/cis_k8s',
policy_template: 'kspm',
enabled: true,
streams: [
{
enabled: true,
data_stream: {
type: 'logs',
dataset: 'cloud_security_posture.findings',
},
id: 'cloudbeat/cis_k8s-cloud_security_posture.findings-6aedf856-bc21-49aa-859a-a0952789f898',
compiled_stream: {
config: {
v1: {
type: 'kspm',
deployment: 'self_managed',
benchmark: 'cis_k8s',
},
},
},
},
],
},
],
vars: {
posture: {
value: 'kspm',
type: 'text',
},
deployment: {
value: 'self_managed',
type: 'text',
},
},
revision: 1,
created_at: '2024-06-03T21:23:23.139Z',
created_by: 'system',
updated_at: '2024-06-03T21:23:23.139Z',
updated_by: 'system',
},
agent_policy: {
id: 'e2f72eea-bf76-4576-bed8-e29d2df102a7',
name: 'Agent policy 1',
agents: 0,
},
rules_count: 92,
},
],
total: 1,
page: 1,
perPage: 100,
});
});

View file

@ -0,0 +1,251 @@
/*
* 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 { http, HttpResponse } from 'msw';
const STATUS_URL = `/internal/cloud_security_posture/status`;
export const notInstalledHandler = http.get(STATUS_URL, () => {
return HttpResponse.json({
cspm: {
status: 'not-installed',
healthyAgents: 1,
installedPackagePolicies: 1,
},
kspm: {
status: 'not-installed',
healthyAgents: 1,
installedPackagePolicies: 1,
},
vuln_mgmt: {
status: 'not-installed',
healthyAgents: 1,
installedPackagePolicies: 1,
},
indicesDetails: [
{
index: 'logs-cloud_security_posture.findings_latest-default',
status: 'empty',
},
{
index: 'logs-cloud_security_posture.findings-default*',
status: 'empty',
},
{
index: 'logs-cloud_security_posture.scores-default',
status: 'empty',
},
{
index: 'logs-cloud_security_posture.vulnerabilities_latest-default',
status: 'empty',
},
],
isPluginInitialized: true,
latestPackageVersion: '1.9.0',
});
});
export const notDeployedHandler = http.get(STATUS_URL, () => {
return HttpResponse.json({
cspm: {
status: 'not-deployed',
healthyAgents: 1,
installedPackagePolicies: 1,
},
kspm: {
status: 'not-deployed',
healthyAgents: 1,
installedPackagePolicies: 1,
},
vuln_mgmt: {
status: 'not-deployed',
healthyAgents: 1,
installedPackagePolicies: 1,
},
indicesDetails: [
{
index: 'logs-cloud_security_posture.findings_latest-default',
status: 'empty',
},
{
index: 'logs-cloud_security_posture.findings-default*',
status: 'empty',
},
{
index: 'logs-cloud_security_posture.scores-default',
status: 'not-empty',
},
{
index: 'logs-cloud_security_posture.vulnerabilities_latest-default',
status: 'empty',
},
],
isPluginInitialized: true,
latestPackageVersion: '1.9.0',
installedPackageVersion: '1.9.0',
});
});
export const indexingHandler = http.get(STATUS_URL, () => {
return HttpResponse.json({
cspm: {
status: 'indexing',
healthyAgents: 1,
installedPackagePolicies: 1,
},
kspm: {
status: 'indexing',
healthyAgents: 1,
installedPackagePolicies: 1,
},
vuln_mgmt: {
status: 'indexing',
healthyAgents: 1,
installedPackagePolicies: 1,
},
indicesDetails: [
{
index: 'logs-cloud_security_posture.findings_latest-default',
status: 'empty',
},
{
index: 'logs-cloud_security_posture.findings-default*',
status: 'not-empty',
},
{
index: 'logs-cloud_security_posture.scores-default',
status: 'empty',
},
{
index: 'logs-cloud_security_posture.vulnerabilities_latest-default',
status: 'empty',
},
],
isPluginInitialized: true,
latestPackageVersion: '1.9.0',
});
});
export const indexTimeoutHandler = http.get(STATUS_URL, () => {
return HttpResponse.json({
cspm: {
status: 'index-timeout',
healthyAgents: 1,
installedPackagePolicies: 1,
},
kspm: {
status: 'index-timeout',
healthyAgents: 1,
installedPackagePolicies: 1,
},
vuln_mgmt: {
status: 'index-timeout',
healthyAgents: 1,
installedPackagePolicies: 1,
},
indicesDetails: [
{
index: 'logs-cloud_security_posture.findings_latest-default',
status: 'empty',
},
{
index: 'logs-cloud_security_posture.findings-default*',
status: 'empty',
},
{
index: 'logs-cloud_security_posture.scores-default',
status: 'empty',
},
{
index: 'logs-cloud_security_posture.vulnerabilities_latest-default',
status: 'empty',
},
],
isPluginInitialized: true,
latestPackageVersion: '1.9.0',
});
});
export const unprivilegedHandler = http.get(STATUS_URL, () => {
return HttpResponse.json({
cspm: {
status: 'unprivileged',
healthyAgents: 1,
installedPackagePolicies: 1,
},
kspm: {
status: 'unprivileged',
healthyAgents: 1,
installedPackagePolicies: 1,
},
vuln_mgmt: {
status: 'unprivileged',
healthyAgents: 1,
installedPackagePolicies: 1,
},
indicesDetails: [
{
index: 'logs-cloud_security_posture.findings_latest-default',
status: 'unprivileged',
},
{
index: 'logs-cloud_security_posture.findings-default*',
status: 'unprivileged',
},
{
index: 'logs-cloud_security_posture.scores-default',
status: 'unprivileged',
},
{
index: 'logs-cloud_security_posture.vulnerabilities_latest-default',
status: 'unprivileged',
},
],
isPluginInitialized: true,
latestPackageVersion: '1.9.0',
});
});
export const indexedHandler = http.get(STATUS_URL, () => {
return HttpResponse.json({
cspm: {
status: 'indexed',
healthyAgents: 1,
installedPackagePolicies: 1,
},
kspm: {
status: 'indexed',
healthyAgents: 1,
installedPackagePolicies: 1,
},
vuln_mgmt: {
status: 'indexed',
healthyAgents: 1,
installedPackagePolicies: 1,
},
indicesDetails: [
{
index: 'logs-cloud_security_posture.findings_latest-default',
status: 'not-empty',
},
{
index: 'logs-cloud_security_posture.findings-default*',
status: 'not-empty',
},
{
index: 'logs-cloud_security_posture.scores-default',
status: 'not-empty',
},
{
index: 'logs-cloud_security_posture.vulnerabilities_latest-default',
status: 'not-empty',
},
],
isPluginInitialized: true,
latestPackageVersion: '1.9.0',
installedPackageVersion: '1.9.0',
});
});

205
yarn.lock
View file

@ -1431,6 +1431,20 @@
resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-1.2.1.tgz#f8b1fbbe79726a4eafa9772ddde147b57f85d177"
integrity sha512-cwwGvLGqvoaOZmoP5+i4v/rbW+rHkguvTehuZyM2p/xpmaNSdT2h3B7kHw33aiffv35t1XrYHIkdJSEkSEMJuA==
"@bundled-es-modules/cookie@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz#c3b82703969a61cf6a46e959a012b2c257f6b164"
integrity sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==
dependencies:
cookie "^0.5.0"
"@bundled-es-modules/statuses@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz#761d10f44e51a94902c4da48675b71a76cc98872"
integrity sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==
dependencies:
statuses "^2.0.1"
"@cbor-extract/cbor-extract-darwin-arm64@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.0.0.tgz#cf0667e4c22111c9d45e16c29964892b12460a76"
@ -2830,6 +2844,43 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
"@inquirer/confirm@^3.0.0":
version "3.1.8"
resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-3.1.8.tgz#db80f23f775d9b980c6de2425dde39f9786bf1d3"
integrity sha512-f3INZ+ca4dQdn+MQiq1yP/mOIR/Oc8BLRYuDh6ciToWd6z4W8yArfzjBCMQ0BPY8PcJKwZxGIt8Z6yNT32eSTw==
dependencies:
"@inquirer/core" "^8.2.1"
"@inquirer/type" "^1.3.2"
"@inquirer/core@^8.2.1":
version "8.2.1"
resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-8.2.1.tgz#ee92c2bf25f378819f56290f8ed8bfef8c6cc94d"
integrity sha512-TIcuQMn2qrtyYe0j136UpHeYpk7AcR/trKeT/7YY0vRgcS9YSfJuQ2+PudPhSofLLsHNnRYAHScQCcVZrJkMqA==
dependencies:
"@inquirer/figures" "^1.0.2"
"@inquirer/type" "^1.3.2"
"@types/mute-stream" "^0.0.4"
"@types/node" "^20.12.12"
"@types/wrap-ansi" "^3.0.0"
ansi-escapes "^4.3.2"
chalk "^4.1.2"
cli-spinners "^2.9.2"
cli-width "^4.1.0"
mute-stream "^1.0.0"
signal-exit "^4.1.0"
strip-ansi "^6.0.1"
wrap-ansi "^6.2.0"
"@inquirer/figures@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.2.tgz#a6af5e9f9969efb9ed3469130566315c36506b8a"
integrity sha512-4F1MBwVr3c/m4bAUef6LgkvBfSjzwH+OfldgHqcuacWwSUetFebM2wi58WfG9uk1rR98U6GwLed4asLJbwdV5w==
"@inquirer/type@^1.3.2":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-1.3.2.tgz#439b0b50c152c89fd369d2a17eff54869b4d79b8"
integrity sha512-5Frickan9c89QbPkSu6I6y8p+9eR6hZkdPahGmNDsTFX8FHLPAozyzCZMKUeW8FyYwnlCKUjqIEqxY+UctARiw==
"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
@ -7305,6 +7356,23 @@
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz#0f164b726869f71da3c594171df5ebc1c4b0a407"
integrity sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==
"@mswjs/cookies@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-1.1.0.tgz#1528eb43630caf83a1d75d5332b30e75e9bb1b5b"
integrity sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==
"@mswjs/interceptors@^0.29.0":
version "0.29.1"
resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.29.1.tgz#e77fc58b5188569041d0440b25c9e9ebb1ccd60a"
integrity sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==
dependencies:
"@open-draft/deferred-promise" "^2.2.0"
"@open-draft/logger" "^0.3.0"
"@open-draft/until" "^2.0.0"
is-node-process "^1.2.0"
outvariant "^1.2.1"
strict-event-emitter "^0.5.1"
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3":
version "2.1.8-no-fsevents.3"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b"
@ -7603,6 +7671,24 @@
dependencies:
"@octokit/openapi-types" "^18.0.0"
"@open-draft/deferred-promise@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd"
integrity sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==
"@open-draft/logger@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@open-draft/logger/-/logger-0.3.0.tgz#2b3ab1242b360aa0adb28b85f5d7da1c133a0954"
integrity sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==
dependencies:
is-node-process "^1.2.0"
outvariant "^1.4.0"
"@open-draft/until@^2.0.0", "@open-draft/until@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.1.0.tgz#0acf32f470af2ceaf47f095cdecd40d68666efda"
integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==
"@opentelemetry/api-metrics@0.31.0", "@opentelemetry/api-metrics@^0.31.0":
version "0.31.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api-metrics/-/api-metrics-0.31.0.tgz#0ed4cf4d7c731f968721c2b303eaf5e9fd42f736"
@ -9783,6 +9869,11 @@
dependencies:
"@types/node" "*"
"@types/cookie@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5"
integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==
"@types/cookiejar@^2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78"
@ -10474,6 +10565,13 @@
resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-0.8.31.tgz#7c86cbf74f7733f9e3bdc28817623927eb386616"
integrity sha512-72flCZJkEJHPwhmpHgg4a0ZBLssMhg5NB0yltRblRlZMo4py3B/u/d7icevc4EeN9MPQUo/dPtuVOoVy9ih6cQ==
"@types/mute-stream@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.4.tgz#77208e56a08767af6c5e1237be8888e2f255c478"
integrity sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==
dependencies:
"@types/node" "*"
"@types/nock@^10.0.3":
version "10.0.3"
resolved "https://registry.yarnpkg.com/@types/nock/-/nock-10.0.3.tgz#dab1d18ffbccfbf2db811dab9584304eeb6e1c4c"
@ -10511,7 +10609,7 @@
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@14 || 16 || 17", "@types/node@20.10.5", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=18.0.0", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0", "@types/node@^18.0.0", "@types/node@^18.11.18":
"@types/node@*", "@types/node@14 || 16 || 17", "@types/node@20.10.5", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=18.0.0", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0", "@types/node@^18.0.0", "@types/node@^18.11.18", "@types/node@^20.12.12":
version "20.10.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2"
integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==
@ -10929,6 +11027,11 @@
resolved "https://registry.yarnpkg.com/@types/stats-lite/-/stats-lite-2.2.0.tgz#bc8190bf9dfa1e16b89eaa2b433c99dff0804de9"
integrity sha512-YV6SS4QC+pbzqjMIV8qVSTDOOazgKBLTVaN+7PfuxELjz/eyzc20KwDVGPrbHt2OcYMA7K2ezLB45Cp6DpNOSQ==
"@types/statuses@^2.0.4":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.5.tgz#f61ab46d5352fd73c863a1ea4e1cef3b0b51ae63"
integrity sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==
"@types/styled-components@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.0.tgz#24d3412ba5395aa06e14fbc93c52f9454cebd0d6"
@ -11139,6 +11242,11 @@
tapable "^2.2.0"
webpack "^5"
"@types/wrap-ansi@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd"
integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==
"@types/ws@*", "@types/ws@^8.5.1":
version "8.5.3"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
@ -11892,12 +12000,12 @@ ansi-colors@^3.0.0:
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.0.tgz#a4ce2b33d6b214b7950d8595c212f12ac9cc569d"
integrity sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==
ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
dependencies:
type-fest "^0.8.1"
type-fest "^0.21.3"
ansi-escapes@^6.2.0:
version "6.2.1"
@ -13922,7 +14030,7 @@ cli-progress@^3.12.0:
dependencies:
string-width "^4.2.3"
cli-spinners@^2.2.0, cli-spinners@^2.5.0:
cli-spinners@^2.2.0, cli-spinners@^2.5.0, cli-spinners@^2.9.2:
version "2.9.2"
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41"
integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==
@ -13957,6 +14065,11 @@ cli-width@^3.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
cli-width@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5"
integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==
client-only@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
@ -18725,7 +18838,7 @@ graphql-tag@^2.12.6:
dependencies:
tslib "^2.1.0"
graphql@^16.6.0:
graphql@^16.6.0, graphql@^16.8.1:
version "16.8.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
@ -19081,6 +19194,11 @@ he@1.2.0, he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
headers-polyfill@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.3.tgz#922a0155de30ecc1f785bcf04be77844ca95ad07"
integrity sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==
heap@^0.2.6:
version "0.2.6"
resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.6.tgz#087e1f10b046932fc8594dd9e6d378afc9d1e5ac"
@ -20083,6 +20201,11 @@ is-nil@^1.0.0:
resolved "https://registry.yarnpkg.com/is-nil/-/is-nil-1.0.1.tgz#2daba29e0b585063875e7b539d071f5b15937969"
integrity sha1-LauingtYUGOHXntTnQcfWxWTeWk=
is-node-process@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.2.0.tgz#ea02a1b90ddb3934a19aea414e88edef7e11d134"
integrity sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==
is-npm@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-6.0.0.tgz#b59e75e8915543ca5d881ecff864077cba095261"
@ -23336,6 +23459,29 @@ msgpackr@^1.9.9:
optionalDependencies:
msgpackr-extract "^3.0.2"
msw@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/msw/-/msw-2.3.1.tgz#bfc73e256ffc2c74ec4381b604abb258df35f32b"
integrity sha512-ocgvBCLn/5l3jpl1lssIb3cniuACJLoOfZu01e3n5dbJrpA5PeeWn28jCLgQDNt6d7QT8tF2fYRzm9JoEHtiig==
dependencies:
"@bundled-es-modules/cookie" "^2.0.0"
"@bundled-es-modules/statuses" "^1.0.1"
"@inquirer/confirm" "^3.0.0"
"@mswjs/cookies" "^1.1.0"
"@mswjs/interceptors" "^0.29.0"
"@open-draft/until" "^2.1.0"
"@types/cookie" "^0.6.0"
"@types/statuses" "^2.0.4"
chalk "^4.1.2"
graphql "^16.8.1"
headers-polyfill "^4.0.2"
is-node-process "^1.2.0"
outvariant "^1.4.2"
path-to-regexp "^6.2.0"
strict-event-emitter "^0.5.1"
type-fest "^4.9.0"
yargs "^17.7.2"
multicast-dns@^7.2.5:
version "7.2.5"
resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced"
@ -23385,6 +23531,11 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
mute-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e"
integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==
nan@^2.18.0:
version "2.18.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554"
@ -24342,6 +24493,11 @@ ospath@^1.2.2:
resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b"
integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=
outvariant@^1.2.1, outvariant@^1.4.0, outvariant@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.4.2.tgz#f54f19240eeb7f15b28263d5147405752d8e2066"
integrity sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==
p-all@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-all/-/p-all-2.1.0.tgz#91419be56b7dee8fe4c5db875d55e0da084244a0"
@ -24737,6 +24893,11 @@ path-to-regexp@^2.2.1:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704"
integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==
path-to-regexp@^6.2.0:
version "6.2.2"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36"
integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@ -28261,10 +28422,10 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
signal-exit@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967"
integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==
signal-exit@^4.0.1, signal-exit@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
simple-bin-help@^1.8.0:
version "1.8.0"
@ -28938,7 +29099,7 @@ stats-lite@^2.2.0:
dependencies:
isnumber "~1.0.0"
statuses@2.0.1:
statuses@2.0.1, statuses@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
@ -29026,6 +29187,11 @@ streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.0, streamx@^2.13.2, streamx@^2.1
optionalDependencies:
bare-events "^2.2.0"
strict-event-emitter@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93"
integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
@ -30286,6 +30452,11 @@ type-fest@^0.20.2:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
type-fest@^0.21.3:
version "0.21.3"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
type-fest@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
@ -30306,10 +30477,10 @@ type-fest@^2.13.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
type-fest@^4.15.0, type-fest@^4.17.0:
version "4.17.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.17.0.tgz#4c1b2c2852d2a40ba8c0236d3afc6fc68229e5bf"
integrity sha512-9flrz1zkfLRH3jO3bLflmTxryzKMxVa7841VeMgBaNQGY6vH4RCcpN/sQLB7mQQYh1GZ5utT2deypMuCy4yicw==
type-fest@^4.15.0, type-fest@^4.17.0, type-fest@^4.9.0:
version "4.18.3"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.18.3.tgz#5249f96e7c2c3f0f1561625f54050e343f1c8f68"
integrity sha512-Q08/0IrpvM+NMY9PA2rti9Jb+JejTddwmwmVQGskAlhtcrw1wsRzoR6ode6mR+OAabNa75w/dxedSUY2mlphaQ==
type-is@~1.6.18:
version "1.6.18"