[Security Solution][Endpoint] Show list of trusted application on the Policy Details (#112182)

* New Artifact Collapsible card and Grid generic components
* Fleet setup test data loader - ignore 409 concurrent installs in data loader for fleet setup
* Adds `ContextMenuWithRouterSupport` prop for `maxWidth` and `truncateText` prop for `ContextMenuItemNaByRouter`
* trustedApps generator loader - use existing policies (if any) when loading TAs
* `CardCompressedHeaderLayout` support for `flushTop` prop
This commit is contained in:
Paul Tavares 2021-10-04 07:48:24 -04:00 committed by GitHub
parent 6bfa2a4c2c
commit 36ce6bda67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 1694 additions and 326 deletions

View file

@ -110,15 +110,20 @@ export const installOrUpgradeEndpointFleetPackage = async (
);
}
if (isFleetBulkInstallError(bulkResp[0])) {
if (bulkResp[0].error instanceof Error) {
const firstError = bulkResp[0];
if (isFleetBulkInstallError(firstError)) {
if (firstError.error instanceof Error) {
throw new EndpointDataLoadingError(
`Installing the Endpoint package failed: ${bulkResp[0].error.message}, exiting`,
`Installing the Endpoint package failed: ${firstError.error.message}, exiting`,
bulkResp
);
}
throw new EndpointDataLoadingError(bulkResp[0].error, bulkResp);
// Ignore `409` (conflicts due to Concurrent install or upgrades of package) errors
if (firstError.statusCode !== 409) {
throw new EndpointDataLoadingError(firstError.error, bulkResp);
}
}
return bulkResp[0] as BulkInstallPackageInfo;

View file

@ -32,7 +32,7 @@ export type GetTrustedAppsListRequest = TypeOf<typeof GetTrustedAppsRequestSchem
/** API request params for retrieving summary of Trusted Apps */
export type GetTrustedAppsSummaryRequest = TypeOf<typeof GetTrustedAppsSummaryRequestSchema.query>;
export interface GetTrustedListAppsResponse {
export interface GetTrustedAppsListResponse {
per_page: number;
page: number;
total: number;

View file

@ -18,7 +18,7 @@ import { i18n } from '@kbn/i18n';
import {
ContextMenuItemNavByRouter,
ContextMenuItemNavByRouterProps,
} from '../context_menu_with_router_support/context_menu_item_nav_by_rotuer';
} from '../context_menu_with_router_support/context_menu_item_nav_by_router';
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
export interface ActionsContextMenuProps {

View file

@ -0,0 +1,123 @@
/*
* 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 { TrustedAppGenerator } from '../../../../common/endpoint/data_generators/trusted_app_generator';
import { cloneDeep } from 'lodash';
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
import { AppContextTestRender, createAppRootMockRenderer } from '../../../common/mock/endpoint';
import React from 'react';
import { ArtifactCardGrid, ArtifactCardGridProps } from './artifact_card_grid';
// FIXME:PT refactor helpers below after merge of PR https://github.com/elastic/kibana/pull/113363
const getCommonItemDataOverrides = () => {
return {
name: 'some internal app',
description: 'this app is trusted by the company',
created_at: new Date('2021-07-01').toISOString(),
};
};
const getTrustedAppProvider = () =>
new TrustedAppGenerator('seed').generate(getCommonItemDataOverrides());
const getExceptionProvider = () => {
// cloneDeep needed because exception mock generator uses state across instances
return cloneDeep(
getExceptionListItemSchemaMock({
...getCommonItemDataOverrides(),
os_types: ['windows'],
updated_at: new Date().toISOString(),
created_by: 'Justa',
updated_by: 'Mara',
entries: [
{
field: 'process.hash.*',
operator: 'included',
type: 'match',
value: '1234234659af249ddf3e40864e9fb241',
},
{
field: 'process.executable.caseless',
operator: 'included',
type: 'match',
value: '/one/two/three',
},
],
tags: ['policy:all'],
})
);
};
describe.each([
['trusted apps', getTrustedAppProvider],
['exceptions/event filters', getExceptionProvider],
])('when using the ArtifactCardGrid component %s', (_, generateItem) => {
let appTestContext: AppContextTestRender;
let renderResult: ReturnType<AppContextTestRender['render']>;
let render: (
props?: Partial<ArtifactCardGridProps>
) => ReturnType<AppContextTestRender['render']>;
let items: ArtifactCardGridProps['items'];
let pageChangeHandler: jest.Mock<ArtifactCardGridProps['onPageChange']>;
let expandCollapseHandler: jest.Mock<ArtifactCardGridProps['onExpandCollapse']>;
let cardComponentPropsProvider: Required<ArtifactCardGridProps>['cardComponentProps'];
beforeEach(() => {
items = Array.from({ length: 5 }, () => generateItem());
pageChangeHandler = jest.fn();
expandCollapseHandler = jest.fn();
cardComponentPropsProvider = jest.fn().mockReturnValue({});
appTestContext = createAppRootMockRenderer();
render = (props = {}) => {
renderResult = appTestContext.render(
<ArtifactCardGrid
{...{
items,
onPageChange: pageChangeHandler!,
onExpandCollapse: expandCollapseHandler!,
cardComponentProps: cardComponentPropsProvider,
'data-test-subj': 'testGrid',
...props,
}}
/>
);
return renderResult;
};
});
it('should render the cards', () => {
render();
expect(renderResult.getAllByTestId('testGrid-card')).toHaveLength(5);
});
it.each([
['header', 'testGrid-header'],
['expand/collapse placeholder', 'testGrid-header-expandCollapsePlaceHolder'],
['name column', 'testGrid-header-layout-title'],
['description column', 'testGrid-header-layout-description'],
['description column', 'testGrid-header-layout-cardActionsPlaceholder'],
])('should display the Grid Header - %s', (__, selector) => {
render();
expect(renderResult.getByTestId(selector)).not.toBeNull();
});
it.todo('should call onPageChange callback when paginating');
it.todo('should use the props provided by cardComponentProps callback');
describe('and when cards are expanded/collapsed', () => {
it.todo('should call onExpandCollapse callback');
it.todo('should provide list of cards that are expanded and collapsed');
it.todo('should show card expanded if card props defined it as such');
});
});

View file

@ -0,0 +1,143 @@
/*
* 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, { ComponentType, memo, useCallback, useMemo } from 'react';
import {
AnyArtifact,
ArtifactEntryCollapsibleCard,
ArtifactEntryCollapsibleCardProps,
} from '../artifact_entry_card';
import { PaginatedContent as _PaginatedContent, PaginatedContentProps } from '../paginated_content';
import { GridHeader } from './components/grid_header';
import { MaybeImmutable } from '../../../../common/endpoint/types';
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
const PaginatedContent: ArtifactsPaginatedComponent = _PaginatedContent;
type ArtifactsPaginatedContentProps = PaginatedContentProps<
AnyArtifact,
typeof ArtifactEntryCollapsibleCard
>;
type ArtifactsPaginatedComponent = ComponentType<ArtifactsPaginatedContentProps>;
interface CardExpandCollapseState {
expanded: MaybeImmutable<AnyArtifact[]>;
collapsed: MaybeImmutable<AnyArtifact[]>;
}
export type ArtifactCardGridCardComponentProps = Omit<
ArtifactEntryCollapsibleCardProps,
'onExpandCollapse' | 'item'
>;
export type ArtifactCardGridProps = Omit<
ArtifactsPaginatedContentProps,
'ItemComponent' | 'itemComponentProps' | 'items' | 'onChange'
> & {
items: MaybeImmutable<AnyArtifact[]>;
/** Callback to handle pagination changes */
onPageChange: ArtifactsPaginatedContentProps['onChange'];
/** callback for handling changes to the card's expand/collapse state */
onExpandCollapse: (state: CardExpandCollapseState) => void;
/**
* Callback to provide additional props for the `ArtifactEntryCollapsibleCard`
*/
cardComponentProps?: (item: MaybeImmutable<AnyArtifact>) => ArtifactCardGridCardComponentProps;
};
export const ArtifactCardGrid = memo<ArtifactCardGridProps>(
({
items: _items,
cardComponentProps,
onPageChange,
onExpandCollapse,
'data-test-subj': dataTestSubj,
...paginatedContentProps
}) => {
const getTestId = useTestIdGenerator(dataTestSubj);
const items = _items as AnyArtifact[];
// The list of card props that the caller can define
type PartialCardProps = Map<AnyArtifact, ArtifactCardGridCardComponentProps>;
const callerDefinedCardProps = useMemo<PartialCardProps>(() => {
const cardProps: PartialCardProps = new Map();
for (const artifact of items) {
cardProps.set(artifact, cardComponentProps ? cardComponentProps(artifact) : {});
}
return cardProps;
}, [cardComponentProps, items]);
// Handling of what is expanded or collapsed is done by looking at the at what the caller card's
// `expanded` prop value was and then invert it for the card that the user clicked expand/collapse
const handleCardExpandCollapse = useCallback(
(item: AnyArtifact) => {
const expanded = [];
const collapsed = [];
for (const [artifact, currentCardProps] of callerDefinedCardProps) {
const currentExpandedState = Boolean(currentCardProps.expanded);
const newExpandedState = artifact === item ? !currentExpandedState : currentExpandedState;
if (newExpandedState) {
expanded.push(artifact);
} else {
collapsed.push(artifact);
}
}
onExpandCollapse({ expanded, collapsed });
},
[callerDefinedCardProps, onExpandCollapse]
);
// Full list of card props that includes the actual artifact and the callbacks
type FullCardProps = Map<AnyArtifact, ArtifactEntryCollapsibleCardProps>;
const fullCardProps = useMemo<FullCardProps>(() => {
const newFullCardProps: FullCardProps = new Map();
for (const [artifact, cardProps] of callerDefinedCardProps) {
newFullCardProps.set(artifact, {
...cardProps,
item: artifact,
onExpandCollapse: () => handleCardExpandCollapse(artifact),
'data-test-subj': cardProps['data-test-subj'] ?? getTestId('card'),
});
}
return newFullCardProps;
}, [callerDefinedCardProps, getTestId, handleCardExpandCollapse]);
const handleItemComponentProps = useCallback(
(item: AnyArtifact): ArtifactEntryCollapsibleCardProps => {
return fullCardProps.get(item)!;
},
[fullCardProps]
);
return (
<>
<GridHeader data-test-subj={getTestId('header')} />
<PaginatedContent
{...paginatedContentProps}
data-test-subj={dataTestSubj}
items={items}
ItemComponent={ArtifactEntryCollapsibleCard}
itemComponentProps={handleItemComponentProps}
onChange={onPageChange}
/>
</>
);
}
);
ArtifactCardGrid.displayName = 'ArtifactCardGrid';

View file

@ -0,0 +1,71 @@
/*
* 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, { memo, useMemo } from 'react';
import { CommonProps, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import styled from 'styled-components';
import { CardCompressedHeaderLayout, CardSectionPanel } from '../../artifact_entry_card';
import { useTestIdGenerator } from '../../hooks/use_test_id_generator';
const GridHeaderContainer = styled(CardSectionPanel)`
padding-top: 0;
padding-bottom: ${({ theme }) => theme.eui.paddingSizes.s};
`;
export type GridHeaderProps = Pick<CommonProps, 'data-test-subj'>;
export const GridHeader = memo<GridHeaderProps>(({ 'data-test-subj': dataTestSubj }) => {
const getTestId = useTestIdGenerator(dataTestSubj);
const expandToggleElement = useMemo(
() => <div data-test-subj={getTestId('expandCollapsePlaceHolder')} style={{ width: '24px' }} />,
[getTestId]
);
return (
<GridHeaderContainer data-test-subj={dataTestSubj}>
<CardCompressedHeaderLayout
expanded={false}
expandToggle={expandToggleElement}
data-test-subj={getTestId('layout')}
flushTop={true}
name={
<EuiText size="xs" data-test-subj={getTestId('name')}>
<strong>
<FormattedMessage
id="xpack.securitySolution.artifactCardGrid.nameColumn"
defaultMessage="Name"
/>
</strong>
</EuiText>
}
description={
<EuiText size="xs" data-test-subj={getTestId('description')}>
<strong>
<FormattedMessage
id="xpack.securitySolution.artifactCardGrid.DescriptionColumn"
defaultMessage="Description"
/>
</strong>
</EuiText>
}
effectScope={
<EuiText size="xs" data-test-subj={getTestId('assignment')}>
<strong>
<FormattedMessage
id="xpack.securitySolution.artifactCardGrid.assignmentColumn"
defaultMessage="Assignment"
/>
</strong>
</EuiText>
}
actionMenu={false}
/>
</GridHeaderContainer>
);
});
GridHeader.displayName = 'GridHeader';

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 './artifact_card_grid';

View file

@ -10,7 +10,7 @@ import { AppContextTestRender, createAppRootMockRenderer } from '../../../common
import { ArtifactEntryCard, ArtifactEntryCardProps } from './artifact_entry_card';
import { act, fireEvent, getByTestId } from '@testing-library/react';
import { AnyArtifact } from './types';
import { isTrustedApp } from './hooks/use_normalized_artifact';
import { isTrustedApp } from './utils';
import { getTrustedAppProvider, getExceptionProvider } from './test_utils';
describe.each([

View file

@ -5,27 +5,22 @@
* 2.0.
*/
import React, { memo, useMemo } from 'react';
import { CommonProps, EuiHorizontalRule, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
import styled from 'styled-components';
import React, { memo } from 'react';
import { CommonProps, EuiHorizontalRule, EuiSpacer, EuiText } from '@elastic/eui';
import { CardHeader, CardHeaderProps } from './components/card_header';
import { CardSubHeader } from './components/card_sub_header';
import { getEmptyValue } from '../../../common/components/empty_value';
import { CriteriaConditions, CriteriaConditionsProps } from './components/criteria_conditions';
import { EffectScopeProps } from './components/effect_scope';
import { ContextMenuItemNavByRouterProps } from '../context_menu_with_router_support/context_menu_item_nav_by_rotuer';
import { AnyArtifact } from './types';
import { AnyArtifact, MenuItemPropsByPolicyId } from './types';
import { useNormalizedArtifact } from './hooks/use_normalized_artifact';
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
const CardContainerPanel = styled(EuiPanel)`
&.artifactEntryCard + &.artifactEntryCard {
margin-top: ${({ theme }) => theme.eui.spacerSizes.l};
}
`;
import { CardContainerPanel } from './components/card_container_panel';
import { CardSectionPanel } from './components/card_section_panel';
import { usePolicyNavLinks } from './hooks/use_policy_nav_links';
import { MaybeImmutable } from '../../../../common/endpoint/types';
export interface ArtifactEntryCardProps extends CommonProps {
item: AnyArtifact;
item: MaybeImmutable<AnyArtifact>;
/**
* The list of actions for the card. Will display an icon with the actions in a menu if defined.
*/
@ -33,52 +28,25 @@ export interface ArtifactEntryCardProps extends CommonProps {
/**
* Information about the policies that are assigned to the `item`'s `effectScope` and that will be
* use to create a navigation link
* used to create the items in the popup context menu. This is a
* `Record<policyId: string, ContextMenuItemNavByRouterProps>`.
*/
policies?: {
[policyId: string]: ContextMenuItemNavByRouterProps;
};
policies?: MenuItemPropsByPolicyId;
}
/**
* Display Artifact Items (ex. Trusted App, Event Filter, etc) as a card.
* This component is a TS Generic that allows you to set what the Item type is
*/
export const ArtifactEntryCard = memo(
({
item,
policies,
actions,
'data-test-subj': dataTestSubj,
...commonProps
}: ArtifactEntryCardProps) => {
const artifact = useNormalizedArtifact(item);
export const ArtifactEntryCard = memo<ArtifactEntryCardProps>(
({ item, policies, actions, 'data-test-subj': dataTestSubj, ...commonProps }) => {
const artifact = useNormalizedArtifact(item as AnyArtifact);
const getTestId = useTestIdGenerator(dataTestSubj);
// create the policy links for each policy listed in the artifact record by grabbing the
// navigation data from the `policies` prop (if any)
const policyNavLinks = useMemo<EffectScopeProps['policies']>(() => {
return artifact.effectScope.type === 'policy'
? artifact?.effectScope.policies.map((id) => {
return policies && policies[id]
? policies[id]
: // else, unable to build a nav link, so just show id
{
children: id,
};
})
: undefined;
}, [artifact.effectScope, policies]);
const policyNavLinks = usePolicyNavLinks(artifact, policies);
return (
<CardContainerPanel
hasBorder={true}
{...commonProps}
data-test-subj={dataTestSubj}
paddingSize="none"
className="artifactEntryCard"
>
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="l">
<CardContainerPanel {...commonProps} data-test-subj={dataTestSubj}>
<CardSectionPanel>
<CardHeader
name={artifact.name}
createdDate={artifact.created_at}
@ -100,17 +68,17 @@ export const ArtifactEntryCard = memo(
{artifact.description || getEmptyValue()}
</p>
</EuiText>
</EuiPanel>
</CardSectionPanel>
<EuiHorizontalRule margin="none" />
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="l">
<CardSectionPanel>
<CriteriaConditions
os={artifact.os as CriteriaConditionsProps['os']}
entries={artifact.entries}
data-test-subj={getTestId('criteriaConditions')}
/>
</EuiPanel>
</CardSectionPanel>
</CardContainerPanel>
);
}

View file

@ -0,0 +1,65 @@
/*
* 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, { memo } from 'react';
import { EuiHorizontalRule } from '@elastic/eui';
import { ArtifactEntryCardProps } from './artifact_entry_card';
import { CardContainerPanel } from './components/card_container_panel';
import { useNormalizedArtifact } from './hooks/use_normalized_artifact';
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
import { CardSectionPanel } from './components/card_section_panel';
import { CriteriaConditions, CriteriaConditionsProps } from './components/criteria_conditions';
import { CardCompressedHeader } from './components/card_compressed_header';
export interface ArtifactEntryCollapsibleCardProps extends ArtifactEntryCardProps {
onExpandCollapse: () => void;
expanded?: boolean;
}
export const ArtifactEntryCollapsibleCard = memo<ArtifactEntryCollapsibleCardProps>(
({
item,
onExpandCollapse,
policies,
actions,
expanded = false,
'data-test-subj': dataTestSubj,
...commonProps
}) => {
const artifact = useNormalizedArtifact(item);
const getTestId = useTestIdGenerator(dataTestSubj);
return (
<CardContainerPanel {...commonProps} data-test-subj={dataTestSubj}>
<CardSectionPanel>
<CardCompressedHeader
artifact={artifact}
actions={actions}
policies={policies}
expanded={expanded}
onExpandCollapse={onExpandCollapse}
data-test-subj={getTestId('header')}
/>
</CardSectionPanel>
{expanded && (
<>
<EuiHorizontalRule margin="xs" />
<CardSectionPanel>
<CriteriaConditions
os={artifact.os as CriteriaConditionsProps['os']}
entries={artifact.entries}
data-test-subj={getTestId('criteriaConditions')}
/>
</CardSectionPanel>
</>
)}
</CardContainerPanel>
);
}
);
ArtifactEntryCollapsibleCard.displayName = 'ArtifactEntryCollapsibleCard';

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { memo } from 'react';
import { CommonProps, EuiFlexItem } from '@elastic/eui';
import { ActionsContextMenu, ActionsContextMenuProps } from '../../actions_context_menu';
export interface CardActionsFlexItemProps extends Pick<CommonProps, 'data-test-subj'> {
/** If defined, then an overflow menu will be shown with the actions provided */
actions?: ActionsContextMenuProps['items'];
}
export const CardActionsFlexItem = memo<CardActionsFlexItemProps>(
({ actions, 'data-test-subj': dataTestSubj }) => {
return actions && actions.length > 0 ? (
<EuiFlexItem grow={false}>
<ActionsContextMenu items={actions} icon="boxesHorizontal" data-test-subj={dataTestSubj} />
</EuiFlexItem>
) : null;
}
);
CardActionsFlexItem.displayName = 'CardActionsFlexItem';

View file

@ -0,0 +1,183 @@
/*
* 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, { memo, ReactNode, useCallback } from 'react';
import { CommonProps, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import styled from 'styled-components';
import { CardExpandButton } from './card_expand_button';
import { TextValueDisplay } from './text_value_display';
import { EffectScope } from './effect_scope';
import { CardActionsFlexItem } from './card_actions_flex_item';
import { ArtifactInfo } from '../types';
import { ArtifactEntryCollapsibleCardProps } from '../artifact_entry_collapsible_card';
import { useTestIdGenerator } from '../../hooks/use_test_id_generator';
import { useCollapsedCssClassNames } from '../hooks/use_collapsed_css_class_names';
import { usePolicyNavLinks } from '../hooks/use_policy_nav_links';
import { getEmptyValue } from '../../../../common/components/empty_value';
export interface CardCompressedHeaderProps
extends Pick<CommonProps, 'data-test-subj'>,
Pick<
ArtifactEntryCollapsibleCardProps,
'onExpandCollapse' | 'expanded' | 'actions' | 'policies'
> {
artifact: ArtifactInfo;
}
export const CardCompressedHeader = memo<CardCompressedHeaderProps>(
({
artifact,
onExpandCollapse,
policies,
actions,
expanded = false,
'data-test-subj': dataTestSubj,
}) => {
const getTestId = useTestIdGenerator(dataTestSubj);
const cssClassNames = useCollapsedCssClassNames(expanded);
const policyNavLinks = usePolicyNavLinks(artifact, policies);
const handleExpandCollapseClick = useCallback(() => {
onExpandCollapse();
}, [onExpandCollapse]);
return (
<EuiFlexGroup responsive={false} alignItems="center" data-test-subj={dataTestSubj}>
<EuiFlexItem grow={false}>
<CardExpandButton
expanded={expanded}
onClick={handleExpandCollapseClick}
data-test-subj={getTestId('expandCollapse')}
/>
</EuiFlexItem>
<EuiFlexItem className={cssClassNames}>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={2} className={cssClassNames} data-test-subj={getTestId('title')}>
<TextValueDisplay bold truncate={!expanded}>
{artifact.name}
</TextValueDisplay>
</EuiFlexItem>
<EuiFlexItem
grow={3}
className={cssClassNames}
data-test-subj={getTestId('description')}
>
<TextValueDisplay truncate={!expanded}>
{artifact.description || getEmptyValue()}
</TextValueDisplay>
</EuiFlexItem>
<EuiFlexItem grow={1}>
<EffectScope policies={policyNavLinks} data-test-subj={getTestId('effectScope')} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<CardActionsFlexItem actions={actions} data-test-subj={getTestId('actions')} />
</EuiFlexGroup>
);
}
);
CardCompressedHeader.displayName = 'CardCompressedHeader';
const ButtonIconPlaceHolder = styled.div`
display: inline-block;
// Sizes below should match that of the Eui's Button Icon, so that it holds the same space.
width: ${({ theme }) => theme.eui.euiIconSizes.large};
height: ${({ theme }) => theme.eui.euiIconSizes.large};
`;
const StyledEuiFlexGroup = styled(EuiFlexGroup)`
&.flushTop,
.flushTop {
padding-top: 0;
margin-top: 0;
}
`;
/**
* Layout used for the compressed card header. Used also in the ArtifactGrid for creating the grid header row
*/
export interface CardCompressedHeaderLayoutProps extends Pick<CommonProps, 'data-test-subj'> {
expanded: boolean;
expandToggle: ReactNode;
name: ReactNode;
description: ReactNode;
effectScope: ReactNode;
/** If no menu is shown, but you want the space for it be preserved, set prop to `false` */
actionMenu?: ReactNode | false;
/**
* When set to `true`, all padding and margin values will be set to zero for the top of the header
* layout, so that all content is flushed to the top
*/
flushTop?: boolean;
}
export const CardCompressedHeaderLayout = memo<CardCompressedHeaderLayoutProps>(
({
expanded,
name,
expandToggle,
effectScope,
actionMenu,
description,
'data-test-subj': dataTestSubj,
flushTop,
}) => {
const getTestId = useTestIdGenerator(dataTestSubj);
const cssClassNames = useCollapsedCssClassNames(expanded);
const flushTopCssClassname = flushTop ? ' flushTop' : '';
return (
<StyledEuiFlexGroup
responsive={false}
alignItems="center"
data-test-subj={dataTestSubj}
className={flushTopCssClassname}
>
<EuiFlexItem grow={false} className={flushTopCssClassname}>
{expandToggle}
</EuiFlexItem>
<EuiFlexItem className={cssClassNames + flushTopCssClassname}>
<EuiFlexGroup alignItems="center" className={flushTopCssClassname}>
<EuiFlexItem
grow={2}
className={cssClassNames + flushTopCssClassname}
data-test-subj={getTestId('title')}
>
{name}
</EuiFlexItem>
<EuiFlexItem
grow={3}
className={cssClassNames + flushTopCssClassname}
data-test-subj={getTestId('description')}
>
{description}
</EuiFlexItem>
<EuiFlexItem
grow={1}
data-test-subj={getTestId('effectScope')}
className={flushTopCssClassname}
>
{effectScope}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{actionMenu === false ? (
<EuiFlexItem
grow={false}
data-test-subj={getTestId('cardActionsPlaceholder')}
className={flushTopCssClassname}
>
<ButtonIconPlaceHolder />
</EuiFlexItem>
) : (
actionMenu
)}
</StyledEuiFlexGroup>
);
}
);
CardCompressedHeaderLayout.displayName = 'CardCompressedHeaderLayout';

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 styled from 'styled-components';
import { EuiPanel } from '@elastic/eui';
import { EuiPanelProps } from '@elastic/eui/src/components/panel/panel';
import React, { memo } from 'react';
export const EuiPanelStyled = styled(EuiPanel)`
&.artifactEntryCard + &.artifactEntryCard {
margin-top: ${({ theme }) => theme.eui.spacerSizes.l};
}
`;
export type CardContainerPanelProps = Exclude<EuiPanelProps, 'hasBorder' | 'paddingSize'>;
export const CardContainerPanel = memo<CardContainerPanelProps>(({ className, ...props }) => {
return (
<EuiPanelStyled
{...props}
hasBorder={true}
paddingSize="none"
className={`artifactEntryCard ${className ?? ''}`}
/>
);
});
CardContainerPanel.displayName = 'CardContainerPanel';

View file

@ -0,0 +1,29 @@
/*
* 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, { memo } from 'react';
import { CommonProps, EuiButtonIcon, EuiButtonIconPropsForButton } from '@elastic/eui';
import { COLLAPSE_ACTION, EXPAND_ACTION } from './translations';
export interface CardExpandButtonProps extends Pick<CommonProps, 'data-test-subj'> {
expanded: boolean;
onClick: EuiButtonIconPropsForButton['onClick'];
}
export const CardExpandButton = memo<CardExpandButtonProps>(
({ expanded, onClick, 'data-test-subj': dataTestSubj }) => {
return (
<EuiButtonIcon
iconType={expanded ? 'arrowUp' : 'arrowDown'}
onClick={onClick}
data-test-subj={dataTestSubj}
aria-label={expanded ? COLLAPSE_ACTION : EXPAND_ACTION}
/>
);
}
);
CardExpandButton.displayName = 'CardExpandButton';

View file

@ -8,15 +8,15 @@
import React, { memo } from 'react';
import { CommonProps, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import { DateFieldValue } from './date_field_value';
import { ActionsContextMenu, ActionsContextMenuProps } from '../../actions_context_menu';
import { useTestIdGenerator } from '../../hooks/use_test_id_generator';
import { CardActionsFlexItem, CardActionsFlexItemProps } from './card_actions_flex_item';
export interface CardHeaderProps extends Pick<CommonProps, 'data-test-subj'> {
export interface CardHeaderProps
extends CardActionsFlexItemProps,
Pick<CommonProps, 'data-test-subj'> {
name: string;
createdDate: string;
updatedDate: string;
/** If defined, then an overflow menu will be shown with the actions provided */
actions?: ActionsContextMenuProps['items'];
}
export const CardHeader = memo<CardHeaderProps>(
@ -52,15 +52,7 @@ export const CardHeader = memo<CardHeaderProps>(
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{actions && actions.length > 0 && (
<EuiFlexItem grow={false}>
<ActionsContextMenu
items={actions}
icon="boxesHorizontal"
data-test-subj={getTestId('actions')}
/>
</EuiFlexItem>
)}
<CardActionsFlexItem actions={actions} data-test-subj={getTestId('actions')} />
</EuiFlexGroup>
);
}

View file

@ -0,0 +1,19 @@
/*
* 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, { memo } from 'react';
import { EuiPanel, EuiPanelProps } from '@elastic/eui';
export type CardSectionPanelProps = Exclude<
EuiPanelProps,
'hasBorder' | 'hasShadow' | 'paddingSize'
>;
export const CardSectionPanel = memo<CardSectionPanelProps>((props) => {
return <EuiPanel {...props} hasBorder={false} hasShadow={false} paddingSize="l" />;
});
CardSectionPanel.displayName = 'CardSectionPanel';

View file

@ -20,7 +20,7 @@ export const CardSubHeader = memo<SubHeaderProps>(
const getTestId = useTestIdGenerator(dataTestSubj);
return (
<EuiFlexGroup alignItems="center" responsive={false} data-test-subj={dataTestSubj}>
<EuiFlexGroup alignItems="center" responsive={true} data-test-subj={dataTestSubj}>
<EuiFlexItem grow={false}>
<TouchedByUsers
createdBy={createdBy}

View file

@ -10,9 +10,15 @@ import { CommonProps, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiIcon } from
import { GLOBAL_EFFECT_SCOPE, POLICY_EFFECT_SCOPE } from './translations';
import { TextValueDisplay } from './text_value_display';
import { ContextMenuWithRouterSupport } from '../../context_menu_with_router_support';
import { ContextMenuItemNavByRouterProps } from '../../context_menu_with_router_support/context_menu_item_nav_by_rotuer';
import { ContextMenuItemNavByRouterProps } from '../../context_menu_with_router_support/context_menu_item_nav_by_router';
import { useTestIdGenerator } from '../../hooks/use_test_id_generator';
// FIXME:PT support being able to show per policy label for Artifacst that have >0 policies, but no menu
// the intent in this component was to also support to be able to display only text for artifacts
// by policy (>0), but **NOT** show the menu.
// So something like: `<EffectScope perPolicyCount={3} />`
// This should dispaly it as "Applied t o 3 policies", but NOT as a menu with links
export interface EffectScopeProps extends Pick<CommonProps, 'data-test-subj'> {
/** If set (even if empty), then effect scope will be policy specific. Else, it shows as global */
policies?: ContextMenuItemNavByRouterProps[];

View file

@ -5,18 +5,30 @@
* 2.0.
*/
import React, { memo, PropsWithChildren } from 'react';
import React, { memo, PropsWithChildren, useMemo } from 'react';
import { EuiText } from '@elastic/eui';
import classNames from 'classnames';
export type TextValueDisplayProps = PropsWithChildren<{
bold?: boolean;
truncate?: boolean;
}>;
/**
* Common component for displaying consistent text across the card. Changes here could impact all of
* display of values on the card
*/
export const TextValueDisplay = memo<TextValueDisplayProps>(({ bold, children }) => {
return <EuiText size="s">{bold ? <strong>{children}</strong> : children}</EuiText>;
export const TextValueDisplay = memo<TextValueDisplayProps>(({ bold, truncate, children }) => {
const cssClassNames = useMemo(() => {
return classNames({
'eui-textTruncate': truncate,
});
}, [truncate]);
return (
<EuiText size="s" className={cssClassNames}>
{bold ? <strong>{children}</strong> : children}
</EuiText>
);
});
TextValueDisplay.displayName = 'TextValueDisplay';

View file

@ -100,3 +100,17 @@ export const OS_LINUX = i18n.translate('xpack.securitySolution.artifactCard.cond
export const OS_MAC = i18n.translate('xpack.securitySolution.artifactCard.conditions.macos', {
defaultMessage: 'Mac',
});
export const EXPAND_ACTION = i18n.translate(
'xpack.securitySolution.artifactExpandableCard.expand',
{
defaultMessage: 'Expand',
}
);
export const COLLAPSE_ACTION = i18n.translate(
'xpack.securitySolution.artifactExpandableCard.collpase',
{
defaultMessage: 'Collapse',
}
);

View file

@ -0,0 +1,21 @@
/*
* 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 { useMemo } from 'react';
import classNames from 'classnames';
/**
* Returns the css classnames that should be applied when the collapsible card is NOT expanded
* @param expanded
*/
export const useCollapsedCssClassNames = (expanded?: boolean): string => {
return useMemo(() => {
return classNames({
'eui-textTruncate': !expanded,
});
}, [expanded]);
};

View file

@ -5,53 +5,18 @@
* 2.0.
*/
/* eslint-disable @typescript-eslint/naming-convention */
import { useMemo } from 'react';
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { AnyArtifact, ArtifactInfo } from '../types';
import { EffectScope, TrustedApp } from '../../../../../common/endpoint/types';
import { tagsToEffectScope } from '../../../../../common/endpoint/service/trusted_apps/mapping';
import { mapToArtifactInfo } from '../utils';
import { MaybeImmutable } from '../../../../../common/endpoint/types';
/**
* Takes in any artifact and return back a new data structure used internally with by the card's components
*
* @param item
*/
export const useNormalizedArtifact = (item: AnyArtifact): ArtifactInfo => {
export const useNormalizedArtifact = (item: MaybeImmutable<AnyArtifact>): ArtifactInfo => {
return useMemo(() => {
const {
name,
created_by,
created_at,
updated_at,
updated_by,
description = '',
entries,
} = item;
return {
name,
created_by,
created_at,
updated_at,
updated_by,
description,
entries: entries as unknown as ArtifactInfo['entries'],
os: isTrustedApp(item) ? item.os : getOsFromExceptionItem(item),
effectScope: isTrustedApp(item) ? item.effectScope : getEffectScopeFromExceptionItem(item),
};
return mapToArtifactInfo(item);
}, [item]);
};
export const isTrustedApp = (item: AnyArtifact): item is TrustedApp => {
return 'effectScope' in item;
};
const getOsFromExceptionItem = (item: ExceptionListItemSchema): string => {
// FYI: Exceptions seem to allow for items to be assigned to more than one OS, unlike Event Filters and Trusted Apps
return item.os_types.join(', ');
};
const getEffectScopeFromExceptionItem = (item: ExceptionListItemSchema): EffectScope => {
return tagsToEffectScope(item.tags);
};

View file

@ -0,0 +1,33 @@
/*
* 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 { useMemo } from 'react';
import { EffectScopeProps } from '../components/effect_scope';
import { ArtifactInfo, MenuItemPropsByPolicyId } from '../types';
import { ContextMenuItemNavByRouterProps } from '../../context_menu_with_router_support/context_menu_item_nav_by_router';
/**
* creates the policy links for each policy listed in the artifact record by grabbing the
* navigation data from the `policies` prop (if any)
*/
export const usePolicyNavLinks = (
artifact: ArtifactInfo,
policies?: MenuItemPropsByPolicyId
): ContextMenuItemNavByRouterProps[] | undefined => {
return useMemo<EffectScopeProps['policies']>(() => {
return artifact.effectScope.type === 'policy'
? artifact?.effectScope.policies.map((id) => {
return policies && policies[id]
? policies[id]
: // else, unable to build a nav link, so just show id
{
children: id,
};
})
: undefined;
}, [artifact.effectScope, policies]);
};

View file

@ -7,3 +7,7 @@
export * from './artifact_entry_card';
export * from './artifact_entry_card_minified';
export * from './artifact_entry_collapsible_card';
export * from './components/card_section_panel';
export * from './types';
export { CardCompressedHeaderLayout } from './components/card_compressed_header';

View file

@ -7,6 +7,7 @@
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { EffectScope, TrustedApp } from '../../../../common/endpoint/types';
import { ContextMenuItemNavByRouterProps } from '../context_menu_with_router_support/context_menu_item_nav_by_router';
export type AnyArtifact = ExceptionListItemSchema | TrustedApp;
@ -27,3 +28,7 @@ export interface ArtifactInfo
value: string;
}>;
}
export interface MenuItemPropsByPolicyId {
[policyId: string]: ContextMenuItemNavByRouterProps;
}

View file

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

View file

@ -0,0 +1,17 @@
/*
* 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 { AnyArtifact } from '../types';
import { TrustedApp } from '../../../../../common/endpoint/types';
/**
* Type guard for `AnyArtifact` to check if it is a trusted app entry
* @param item
*/
export const isTrustedApp = (item: AnyArtifact): item is TrustedApp => {
return 'effectScope' in item;
};

View file

@ -0,0 +1,40 @@
/*
* 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 { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { AnyArtifact, ArtifactInfo } from '../types';
import { EffectScope, MaybeImmutable } from '../../../../../common/endpoint/types';
import { tagsToEffectScope } from '../../../../../common/endpoint/service/trusted_apps/mapping';
import { isTrustedApp } from './is_trusted_app';
export const mapToArtifactInfo = (_item: MaybeImmutable<AnyArtifact>): ArtifactInfo => {
const item = _item as AnyArtifact;
// eslint-disable-next-line @typescript-eslint/naming-convention
const { name, created_by, created_at, updated_at, updated_by, description = '', entries } = item;
return {
name,
created_by,
created_at,
updated_at,
updated_by,
description,
entries: entries as unknown as ArtifactInfo['entries'],
os: isTrustedApp(item) ? item.os : getOsFromExceptionItem(item),
effectScope: isTrustedApp(item) ? item.effectScope : getEffectScopeFromExceptionItem(item),
};
};
const getOsFromExceptionItem = (item: ExceptionListItemSchema): string => {
// FYI: Exceptions seem to allow for items to be assigned to more than one OS, unlike Event Filters and Trusted Apps
return item.os_types.join(', ');
};
const getEffectScopeFromExceptionItem = (item: ExceptionListItemSchema): EffectScope => {
return tagsToEffectScope(item.tags);
};

View file

@ -15,6 +15,12 @@ export interface ContextMenuItemNavByRouterProps extends EuiContextMenuItemProps
navigateAppId?: string;
/** Additional options for the navigation action via react-router */
navigateOptions?: NavigateToAppOptions;
/**
* if `true`, the `children` will be wrapped in a `div` that contains CSS Classname `eui-textTruncate`.
* **NOTE**: When this component is used in combination with `ContextMenuWithRouterSupport` and `maxWidth`
* is set on the menu component, this prop will be overridden
*/
textTruncate?: boolean;
children: React.ReactNode;
}
@ -23,7 +29,7 @@ export interface ContextMenuItemNavByRouterProps extends EuiContextMenuItemProps
* allow navigation to a URL path via React Router
*/
export const ContextMenuItemNavByRouter = memo<ContextMenuItemNavByRouterProps>(
({ navigateAppId, navigateOptions, onClick, children, ...otherMenuItemProps }) => {
({ navigateAppId, navigateOptions, onClick, textTruncate, children, ...otherMenuItemProps }) => {
const handleOnClickViaNavigateToApp = useNavigateToAppEventHandler(navigateAppId ?? '', {
...navigateOptions,
onClick,
@ -34,7 +40,19 @@ export const ContextMenuItemNavByRouter = memo<ContextMenuItemNavByRouterProps>(
{...otherMenuItemProps}
onClick={navigateAppId ? handleOnClickViaNavigateToApp : onClick}
>
{children}
{textTruncate ? (
<div
className="eui-textTruncate"
{
/* Add the html `title` prop if children is a string */
...('string' === typeof children ? { title: children } : {})
}
>
{children}
</div>
) : (
children
)}
</EuiContextMenuItem>
);
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { memo, useCallback, useMemo, useState } from 'react';
import React, { CSSProperties, HTMLAttributes, memo, useCallback, useMemo, useState } from 'react';
import {
CommonProps,
EuiContextMenuPanel,
@ -16,13 +16,19 @@ import {
import {
ContextMenuItemNavByRouter,
ContextMenuItemNavByRouterProps,
} from './context_menu_item_nav_by_rotuer';
} from './context_menu_item_nav_by_router';
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
export interface ContextMenuWithRouterSupportProps
extends CommonProps,
Pick<EuiPopoverProps, 'button' | 'anchorPosition' | 'panelPaddingSize'> {
items: ContextMenuItemNavByRouterProps[];
/**
* The max width for the popup menu. Default is `32ch`.
* **Note** that when used (default behaviour), all menu item's `truncateText` prop will be
* overwritten to `true`. Setting this prop's value to `undefined` will suppress the default behaviour.
*/
maxWidth?: CSSProperties['maxWidth'];
}
/**
@ -31,7 +37,7 @@ export interface ContextMenuWithRouterSupportProps
* Menu also supports automatically closing the popup when an item is clicked.
*/
export const ContextMenuWithRouterSupport = memo<ContextMenuWithRouterSupportProps>(
({ items, button, panelPaddingSize, anchorPosition, ...commonProps }) => {
({ items, button, panelPaddingSize, anchorPosition, maxWidth = '32ch', ...commonProps }) => {
const getTestId = useTestIdGenerator(commonProps['data-test-subj']);
const [isOpen, setIsOpen] = useState(false);
@ -47,6 +53,7 @@ export const ContextMenuWithRouterSupport = memo<ContextMenuWithRouterSupportPro
return (
<ContextMenuItemNavByRouter
{...itemProps}
textTruncate={Boolean(maxWidth) || itemProps.textTruncate}
onClick={(ev) => {
handleCloseMenu();
if (itemProps.onClick) {
@ -56,7 +63,20 @@ export const ContextMenuWithRouterSupport = memo<ContextMenuWithRouterSupportPro
/>
);
});
}, [handleCloseMenu, items]);
}, [handleCloseMenu, items, maxWidth]);
type AdditionalPanelProps = Partial<EuiContextMenuPanelProps & HTMLAttributes<HTMLDivElement>>;
const additionalContextMenuPanelProps = useMemo<AdditionalPanelProps>(() => {
const newAdditionalProps: AdditionalPanelProps = {
style: {},
};
if (maxWidth) {
newAdditionalProps.style!.maxWidth = maxWidth;
}
return newAdditionalProps;
}, [maxWidth]);
return (
<EuiPopover
@ -73,7 +93,7 @@ export const ContextMenuWithRouterSupport = memo<ContextMenuWithRouterSupportPro
isOpen={isOpen}
closePopover={handleCloseMenu}
>
<EuiContextMenuPanel items={menuItems} />
<EuiContextMenuPanel {...additionalContextMenuPanelProps} items={menuItems} />
</EuiPopover>
);
}

View file

@ -14,7 +14,7 @@ import {
EuiPopoverProps,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ContextMenuItemNavByRouter } from '../../../../components/context_menu_with_router_support/context_menu_item_nav_by_rotuer';
import { ContextMenuItemNavByRouter } from '../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router';
import { HostMetadata } from '../../../../../../common/endpoint/types';
import { useEndpointActionItems } from '../hooks';

View file

@ -10,7 +10,7 @@ import { EuiContextMenuPanel, EuiButton, EuiPopover } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { useEndpointActionItems, useEndpointSelector } from '../../hooks';
import { detailsData } from '../../../store/selectors';
import { ContextMenuItemNavByRouter } from '../../../../../components/context_menu_with_router_support/context_menu_item_nav_by_rotuer';
import { ContextMenuItemNavByRouter } from '../../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router';
export const ActionsMenu = React.memo<{}>(() => {
const endpointDetails = useEndpointSelector(detailsData);

View file

@ -14,7 +14,7 @@ import { HostMetadata, MaybeImmutable } from '../../../../../../common/endpoint/
import { useEndpointSelector } from './hooks';
import { agentPolicies, uiQueryParams } from '../../store/selectors';
import { useAppUrl } from '../../../../../common/lib/kibana/hooks';
import { ContextMenuItemNavByRouterProps } from '../../../../components/context_menu_with_router_support/context_menu_item_nav_by_rotuer';
import { ContextMenuItemNavByRouterProps } from '../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router';
import { isEndpointHostIsolated } from '../../../../../common/utils/validators';
import { useLicense } from '../../../../../common/hooks/use_license';
import { isIsolationSupported } from '../../../../../../common/endpoint/service/host_isolation/utils';

View file

@ -5,14 +5,17 @@
* 2.0.
*/
import { Action } from 'redux';
import { AsyncResourceState } from '../../../../../state';
import {
PostTrustedAppCreateResponse,
GetTrustedListAppsResponse,
GetTrustedAppsListResponse,
} from '../../../../../../../common/endpoint/types';
import { PolicyArtifactsState } from '../../../types';
export interface PolicyArtifactsAssignableListPageDataChanged {
type: 'policyArtifactsAssignableListPageDataChanged';
payload: AsyncResourceState<GetTrustedListAppsResponse>;
payload: AsyncResourceState<GetTrustedAppsListResponse>;
}
export interface PolicyArtifactsUpdateTrustedApps {
@ -37,9 +40,28 @@ export interface PolicyArtifactsAssignableListPageDataFilter {
payload: { filter: string };
}
export interface AssignedTrustedAppsListStateChanged
extends Action<'assignedTrustedAppsListStateChanged'> {
payload: PolicyArtifactsState['assignedList'];
}
export interface PolicyDetailsListOfAllPoliciesStateChanged
extends Action<'policyDetailsListOfAllPoliciesStateChanged'> {
payload: PolicyArtifactsState['policies'];
}
export type PolicyDetailsTrustedAppsForceListDataRefresh =
Action<'policyDetailsTrustedAppsForceListDataRefresh'>;
/**
* All of the possible actions for Trusted Apps under the Policy Details store
*/
export type PolicyTrustedAppsAction =
| PolicyArtifactsAssignableListPageDataChanged
| PolicyArtifactsUpdateTrustedApps
| PolicyArtifactsUpdateTrustedAppsChanged
| PolicyArtifactsAssignableListExistDataChanged
| PolicyArtifactsAssignableListPageDataFilter;
| PolicyArtifactsAssignableListPageDataFilter
| AssignedTrustedAppsListStateChanged
| PolicyDetailsListOfAllPoliciesStateChanged
| PolicyDetailsTrustedAppsForceListDataRefresh;

View file

@ -6,9 +6,10 @@
*/
import { ImmutableMiddlewareFactory } from '../../../../../../common/store';
import { PolicyDetailsState } from '../../../types';
import { MiddlewareRunnerContext, PolicyDetailsState } from '../../../types';
import { policyTrustedAppsMiddlewareRunner } from './policy_trusted_apps_middleware';
import { policySettingsMiddlewareRunner } from './policy_settings_middleware';
import { TrustedAppsHttpService } from '../../../../trusted_apps/service';
export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory<PolicyDetailsState> = (
coreStart
@ -16,7 +17,13 @@ export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory<PolicyDe
return (store) => (next) => async (action) => {
next(action);
policySettingsMiddlewareRunner(coreStart, store, action);
policyTrustedAppsMiddlewareRunner(coreStart, store, action);
const trustedAppsService = new TrustedAppsHttpService(coreStart.http);
const middlewareContext: MiddlewareRunnerContext = {
coreStart,
trustedAppsService,
};
policySettingsMiddlewareRunner(middlewareContext, store, action);
policyTrustedAppsMiddlewareRunner(middlewareContext, store, action);
};
};

View file

@ -27,7 +27,7 @@ import { NewPolicyData, PolicyData } from '../../../../../../../common/endpoint/
import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/service/policy';
export const policySettingsMiddlewareRunner: MiddlewareRunner = async (
coreStart,
{ coreStart },
{ dispatch, getState },
action
) => {

View file

@ -7,30 +7,95 @@
import pMap from 'p-map';
import { find, isEmpty } from 'lodash/fp';
import { PolicyDetailsState, MiddlewareRunner } from '../../../types';
import {
PolicyDetailsState,
MiddlewareRunner,
GetPolicyListResponse,
MiddlewareRunnerContext,
PolicyAssignedTrustedApps,
PolicyDetailsStore,
} from '../../../types';
import {
policyIdFromParams,
isOnPolicyTrustedAppsPage,
getCurrentArtifactsLocation,
getAssignableArtifactsList,
doesPolicyTrustedAppsListNeedUpdate,
getCurrentPolicyAssignedTrustedAppsState,
getLatestLoadedPolicyAssignedTrustedAppsState,
getTrustedAppsPolicyListState,
isPolicyTrustedAppListLoading,
getCurrentArtifactsLocation,
isOnPolicyTrustedAppsView,
getCurrentUrlLocationPaginationParams,
} from '../selectors';
import {
ImmutableArray,
ImmutableObject,
PostTrustedAppCreateRequest,
TrustedApp,
Immutable,
} from '../../../../../../../common/endpoint/types';
import { ImmutableMiddlewareAPI } from '../../../../../../common/store';
import { TrustedAppsHttpService, TrustedAppsService } from '../../../../trusted_apps/service';
import { TrustedAppsService } from '../../../../trusted_apps/service';
import {
createLoadedResourceState,
createLoadingResourceState,
createUninitialisedResourceState,
createFailedResourceState,
isLoadingResourceState,
isUninitialisedResourceState,
} from '../../../../../state';
import { parseQueryFilterToKQL } from '../../../../../common/utils';
import { SEARCHABLE_FIELDS } from '../../../../trusted_apps/constants';
import { PolicyDetailsAction } from '../action';
import { ServerApiError } from '../../../../../../common/types';
/** Runs all middleware actions associated with the Trusted Apps view in Policy Details */
export const policyTrustedAppsMiddlewareRunner: MiddlewareRunner = async (
context,
store,
action
) => {
const state = store.getState();
/* -----------------------------------------------------------
If not on the Trusted Apps Policy view, then just return
----------------------------------------------------------- */
if (!isOnPolicyTrustedAppsView(state)) {
return;
}
const { trustedAppsService } = context;
switch (action.type) {
case 'userChangedUrl':
fetchPolicyTrustedAppsIfNeeded(context, store);
fetchAllPoliciesIfNeeded(context, store);
if (action.type === 'userChangedUrl' && getCurrentArtifactsLocation(state).show === 'list') {
await searchTrustedApps(store, trustedAppsService);
}
break;
case 'policyDetailsTrustedAppsForceListDataRefresh':
fetchPolicyTrustedAppsIfNeeded(context, store, true);
break;
case 'policyArtifactsUpdateTrustedApps':
if (getCurrentArtifactsLocation(state).show === 'list') {
await updateTrustedApps(store, trustedAppsService, action.payload.trustedAppIds);
}
break;
case 'policyArtifactsAssignableListPageDataFilter':
if (getCurrentArtifactsLocation(state).show === 'list') {
await searchTrustedApps(store, trustedAppsService, action.payload.filter);
}
break;
}
};
const checkIfThereAreAssignableTrustedApps = async (
store: ImmutableMiddlewareAPI<PolicyDetailsState, PolicyDetailsAction>,
@ -172,6 +237,8 @@ const updateTrustedApps = async (
type: 'policyArtifactsUpdateTrustedAppsChanged',
payload: createLoadedResourceState(updatedTrustedApps),
});
store.dispatch({ type: 'policyDetailsTrustedAppsForceListDataRefresh' });
} catch (err) {
store.dispatch({
type: 'policyArtifactsUpdateTrustedAppsChanged',
@ -182,31 +249,89 @@ const updateTrustedApps = async (
}
};
export const policyTrustedAppsMiddlewareRunner: MiddlewareRunner = async (
coreStart,
store,
action
const fetchPolicyTrustedAppsIfNeeded = async (
{ trustedAppsService }: MiddlewareRunnerContext,
{ getState, dispatch }: PolicyDetailsStore,
forceFetch: boolean = false
) => {
const http = coreStart.http;
const trustedAppsService = new TrustedAppsHttpService(http);
const state = store.getState();
if (
action.type === 'userChangedUrl' &&
isOnPolicyTrustedAppsPage(state) &&
getCurrentArtifactsLocation(state).show === 'list'
) {
await searchTrustedApps(store, trustedAppsService);
} else if (
action.type === 'policyArtifactsUpdateTrustedApps' &&
isOnPolicyTrustedAppsPage(state) &&
getCurrentArtifactsLocation(state).show === 'list'
) {
await updateTrustedApps(store, trustedAppsService, action.payload.trustedAppIds);
} else if (
action.type === 'policyArtifactsAssignableListPageDataFilter' &&
isOnPolicyTrustedAppsPage(state) &&
getCurrentArtifactsLocation(state).show === 'list'
) {
await searchTrustedApps(store, trustedAppsService, action.payload.filter);
const state = getState();
if (isPolicyTrustedAppListLoading(state)) {
return;
}
if (forceFetch || doesPolicyTrustedAppsListNeedUpdate(state)) {
dispatch({
type: 'assignedTrustedAppsListStateChanged',
// @ts-ignore will be fixed when AsyncResourceState is refactored (#830)
payload: createLoadingResourceState(getCurrentPolicyAssignedTrustedAppsState(state)),
});
try {
const urlLocationData = getCurrentUrlLocationPaginationParams(state);
const policyId = policyIdFromParams(state);
const fetchResponse = await trustedAppsService.getTrustedAppsList({
page: urlLocationData.page_index + 1,
per_page: urlLocationData.page_size,
kuery: `((exception-list-agnostic.attributes.tags:"policy:${policyId}") OR (exception-list-agnostic.attributes.tags:"policy:all"))${
urlLocationData.filter ? ` AND (${urlLocationData.filter})` : ''
}`,
});
dispatch({
type: 'assignedTrustedAppsListStateChanged',
payload: createLoadedResourceState<Immutable<PolicyAssignedTrustedApps>>({
location: urlLocationData,
artifacts: fetchResponse,
}),
});
} catch (error) {
dispatch({
type: 'assignedTrustedAppsListStateChanged',
payload: createFailedResourceState<Immutable<PolicyAssignedTrustedApps>>(
error as ServerApiError,
getLatestLoadedPolicyAssignedTrustedAppsState(getState())
),
});
}
}
};
const fetchAllPoliciesIfNeeded = async (
{ trustedAppsService }: MiddlewareRunnerContext,
{ getState, dispatch }: PolicyDetailsStore
) => {
const state = getState();
const currentPoliciesState = getTrustedAppsPolicyListState(state);
const isLoading = isLoadingResourceState(currentPoliciesState);
const hasBeenLoaded = !isUninitialisedResourceState(currentPoliciesState);
if (isLoading || hasBeenLoaded) {
return;
}
dispatch({
type: 'policyDetailsListOfAllPoliciesStateChanged',
// @ts-ignore will be fixed when AsyncResourceState is refactored (#830)
payload: createLoadingResourceState(currentPoliciesState),
});
try {
const policyList = await trustedAppsService.getPolicyList({
query: {
page: 1,
perPage: 1000,
},
});
dispatch({
type: 'policyDetailsListOfAllPoliciesStateChanged',
payload: createLoadedResourceState(policyList),
});
} catch (error) {
dispatch({
type: 'policyDetailsListOfAllPoliciesStateChanged',
payload: createFailedResourceState<GetPolicyListResponse>(error.body || error),
});
}
};

View file

@ -37,5 +37,7 @@ export const initialPolicyDetailsState: () => Immutable<PolicyDetailsState> = ()
assignableList: createUninitialisedResourceState(),
trustedAppsToUpdate: createUninitialisedResourceState(),
assignableListEntriesExist: createUninitialisedResourceState(),
assignedList: createUninitialisedResourceState(),
policies: createUninitialisedResourceState(),
},
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { PolicyDetailsState } from '../../../types';
import { initialPolicyDetailsState } from '../reducer/initial_policy_details_state';
import { initialPolicyDetailsState } from './initial_policy_details_state';
import { policyTrustedAppsReducer } from './trusted_apps_reducer';
import { ImmutableObject } from '../../../../../../../common/endpoint/types';
@ -16,12 +16,20 @@ import {
createFailedResourceState,
} from '../../../../../state';
import { getMockListResponse, getAPIError, getMockCreateResponse } from '../../../test_utils';
import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing';
describe('policy trusted apps reducer', () => {
let initialState: ImmutableObject<PolicyDetailsState>;
beforeEach(() => {
initialState = initialPolicyDetailsState();
initialState = {
...initialPolicyDetailsState(),
location: {
pathname: getPolicyDetailsArtifactsListPath('abc'),
search: '',
hash: '',
},
};
});
describe('PolicyTrustedApps', () => {

View file

@ -9,11 +9,28 @@ import { ImmutableReducer } from '../../../../../../common/store';
import { PolicyDetailsState } from '../../../types';
import { AppAction } from '../../../../../../common/store/actions';
import { initialPolicyDetailsState } from './initial_policy_details_state';
import { isUninitialisedResourceState } from '../../../../../state';
import { getCurrentPolicyAssignedTrustedAppsState, isOnPolicyTrustedAppsView } from '../selectors';
export const policyTrustedAppsReducer: ImmutableReducer<PolicyDetailsState, AppAction> = (
state = initialPolicyDetailsState(),
action
) => {
/* ----------------------------------------------------------
If not on the Trusted Apps Policy view, then just return
---------------------------------------------------------- */
if (!isOnPolicyTrustedAppsView(state)) {
// If the artifacts state namespace needs resetting, then do it now
if (!isUninitialisedResourceState(getCurrentPolicyAssignedTrustedAppsState(state))) {
return {
...state,
artifacts: initialPolicyDetailsState().artifacts,
};
}
return state;
}
if (action.type === 'policyArtifactsAssignableListPageDataChanged') {
return {
...state,
@ -44,5 +61,25 @@ export const policyTrustedAppsReducer: ImmutableReducer<PolicyDetailsState, AppA
};
}
if (action.type === 'assignedTrustedAppsListStateChanged') {
return {
...state,
artifacts: {
...state?.artifacts,
assignedList: action.payload,
},
};
}
if (action.type === 'policyDetailsListOfAllPoliciesStateChanged') {
return {
...state,
artifacts: {
...state.artifacts,
policies: action.payload,
},
};
}
return state;
};

View file

@ -7,3 +7,4 @@
export * from './policy_settings_selectors';
export * from './trusted_apps_selectors';
export * from './policy_common_selectors';

View file

@ -0,0 +1,50 @@
/*
* 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 { matchPath } from 'react-router-dom';
import { createSelector } from 'reselect';
import { PolicyDetailsSelector, PolicyDetailsState } from '../../../types';
import {
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
} from '../../../../../common/constants';
/**
* Returns current artifacts location
*/
export const getCurrentArtifactsLocation: PolicyDetailsSelector<
PolicyDetailsState['artifacts']['location']
> = (state) => state.artifacts.location;
export const getUrlLocationPathname: PolicyDetailsSelector<string | undefined> = (state) =>
state.location?.pathname;
/** Returns a boolean of whether the user is on the policy form page or not */
export const isOnPolicyFormView: PolicyDetailsSelector<boolean> = createSelector(
getUrlLocationPathname,
(pathname) => {
return (
matchPath(pathname ?? '', {
path: MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
exact: true,
}) !== null
);
}
);
/** Returns a boolean of whether the user is on the policy details page or not */
export const isOnPolicyTrustedAppsView: PolicyDetailsSelector<boolean> = createSelector(
getUrlLocationPathname,
(pathname) => {
return (
matchPath(pathname ?? '', {
path: MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
exact: true,
}) !== null
);
}
);

View file

@ -23,8 +23,8 @@ import {
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
} from '../../../../../common/constants';
import { ManagementRoutePolicyDetailsParams } from '../../../../../types';
import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/service/policy/get_policy_data_for_update';
import { isOnPolicyTrustedAppsPage } from './trusted_apps_selectors';
import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/service/policy';
import { isOnPolicyTrustedAppsView, isOnPolicyFormView } from './policy_common_selectors';
/** Returns the policy details */
export const policyDetails = (state: Immutable<PolicyDetailsState>) => state.policyItem;
@ -81,19 +81,9 @@ export const needsToRefresh = (state: Immutable<PolicyDetailsState>): boolean =>
return !state.policyItem && !state.apiError;
};
/** Returns a boolean of whether the user is on the policy form page or not */
export const isOnPolicyFormPage = (state: Immutable<PolicyDetailsState>) => {
return (
matchPath(state.location?.pathname ?? '', {
path: MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
exact: true,
}) !== null
);
};
/** Returns a boolean of whether the user is on some of the policy details page or not */
export const isOnPolicyDetailsPage = (state: Immutable<PolicyDetailsState>) =>
isOnPolicyFormPage(state) || isOnPolicyTrustedAppsPage(state);
isOnPolicyFormView(state) || isOnPolicyTrustedAppsView(state);
/** Returns the license info fetched from the license service */
export const license = (state: Immutable<PolicyDetailsState>) => {

View file

@ -6,9 +6,8 @@
*/
import { PolicyDetailsState } from '../../../types';
import { initialPolicyDetailsState } from '../reducer/initial_policy_details_state';
import { initialPolicyDetailsState } from '../reducer';
import {
getCurrentArtifactsLocation,
getAssignableArtifactsList,
getAssignableArtifactsListIsLoading,
getUpdateArtifactsIsLoading,
@ -17,8 +16,8 @@ import {
getAssignableArtifactsListExist,
getAssignableArtifactsListExistIsLoading,
getUpdateArtifacts,
isOnPolicyTrustedAppsPage,
} from './trusted_apps_selectors';
import { getCurrentArtifactsLocation, isOnPolicyTrustedAppsView } from './policy_common_selectors';
import { ImmutableObject } from '../../../../../../../common/endpoint/types';
import {
@ -39,7 +38,7 @@ describe('policy trusted apps selectors', () => {
describe('isOnPolicyTrustedAppsPage()', () => {
it('when location is on policy trusted apps page', () => {
const isOnPage = isOnPolicyTrustedAppsPage({
const isOnPage = isOnPolicyTrustedAppsView({
...initialState,
location: {
pathname: MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
@ -50,7 +49,7 @@ describe('policy trusted apps selectors', () => {
expect(isOnPage).toBeFalsy();
});
it('when location is not on policy trusted apps page', () => {
const isOnPage = isOnPolicyTrustedAppsPage({
const isOnPage = isOnPolicyTrustedAppsView({
...initialState,
location: { pathname: '', search: '', hash: '' },
});

View file

@ -5,35 +5,48 @@
* 2.0.
*/
import { matchPath } from 'react-router-dom';
import { PolicyDetailsArtifactsPageLocation, PolicyDetailsState } from '../../../types';
import { createSelector } from 'reselect';
import { Pagination } from '@elastic/eui';
import {
PolicyArtifactsState,
PolicyAssignedTrustedApps,
PolicyDetailsArtifactsPageListLocationParams,
PolicyDetailsSelector,
PolicyDetailsState,
} from '../../../types';
import {
Immutable,
ImmutableArray,
PostTrustedAppCreateResponse,
GetTrustedListAppsResponse,
GetTrustedAppsListResponse,
PolicyData,
} from '../../../../../../../common/endpoint/types';
import { MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH } from '../../../../../common/constants';
import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../../../common/constants';
import {
getLastLoadedResourceState,
isFailedResourceState,
isLoadedResourceState,
isLoadingResourceState,
LoadedResourceState,
} from '../../../../../state';
import { getCurrentArtifactsLocation } from './policy_common_selectors';
/**
* Returns current artifacts location
*/
export const getCurrentArtifactsLocation = (
state: Immutable<PolicyDetailsState>
): Immutable<PolicyDetailsArtifactsPageLocation> => state.artifacts.location;
export const doesPolicyHaveTrustedApps = (
state: PolicyDetailsState
): { loading: boolean; hasTrustedApps: boolean } => {
// TODO: implement empty state (task #1645)
return {
loading: false,
hasTrustedApps: true,
};
};
/**
* Returns current assignable artifacts list
*/
export const getAssignableArtifactsList = (
state: Immutable<PolicyDetailsState>
): Immutable<GetTrustedListAppsResponse> | undefined =>
): Immutable<GetTrustedAppsListResponse> | undefined =>
getLastLoadedResourceState(state.artifacts.assignableList)?.data;
/**
@ -92,12 +105,79 @@ export const getUpdateArtifacts = (
: undefined;
};
/** Returns a boolean of whether the user is on the policy details page or not */
export const isOnPolicyTrustedAppsPage = (state: Immutable<PolicyDetailsState>) => {
return (
matchPath(state.location?.pathname ?? '', {
path: MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
exact: true,
}) !== null
);
export const getCurrentPolicyAssignedTrustedAppsState: PolicyDetailsSelector<
PolicyArtifactsState['assignedList']
> = (state) => {
return state.artifacts.assignedList;
};
export const getLatestLoadedPolicyAssignedTrustedAppsState: PolicyDetailsSelector<
undefined | LoadedResourceState<PolicyAssignedTrustedApps>
> = createSelector(getCurrentPolicyAssignedTrustedAppsState, (currentAssignedTrustedAppsState) => {
return getLastLoadedResourceState(currentAssignedTrustedAppsState);
});
export const getCurrentUrlLocationPaginationParams: PolicyDetailsSelector<PolicyDetailsArtifactsPageListLocationParams> =
// eslint-disable-next-line @typescript-eslint/naming-convention
createSelector(getCurrentArtifactsLocation, ({ filter, page_index, page_size }) => {
return { filter, page_index, page_size };
});
export const doesPolicyTrustedAppsListNeedUpdate: PolicyDetailsSelector<boolean> = createSelector(
getCurrentPolicyAssignedTrustedAppsState,
getCurrentUrlLocationPaginationParams,
(assignedListState, locationData) => {
return (
!isLoadedResourceState(assignedListState) ||
(isLoadedResourceState(assignedListState) &&
(
Object.keys(locationData) as Array<keyof PolicyDetailsArtifactsPageListLocationParams>
).some((key) => assignedListState.data.location[key] !== locationData[key]))
);
}
);
export const isPolicyTrustedAppListLoading: PolicyDetailsSelector<boolean> = createSelector(
getCurrentPolicyAssignedTrustedAppsState,
(assignedState) => isLoadingResourceState(assignedState)
);
export const getPolicyTrustedAppList: PolicyDetailsSelector<GetTrustedAppsListResponse['data']> =
createSelector(getLatestLoadedPolicyAssignedTrustedAppsState, (assignedState) => {
return assignedState?.data.artifacts.data ?? [];
});
export const getPolicyTrustedAppsListPagination: PolicyDetailsSelector<Pagination> = createSelector(
getLatestLoadedPolicyAssignedTrustedAppsState,
(currentAssignedTrustedAppsState) => {
const trustedAppsApiResponse = currentAssignedTrustedAppsState?.data.artifacts;
return {
// Trusted apps api is `1` based for page - need to subtract here for `Pagination` component
pageIndex: trustedAppsApiResponse?.page ? trustedAppsApiResponse.page - 1 : 0,
pageSize: trustedAppsApiResponse?.per_page ?? MANAGEMENT_PAGE_SIZE_OPTIONS[0],
totalItemCount: trustedAppsApiResponse?.total || 0,
pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS],
};
}
);
export const getTrustedAppsPolicyListState: PolicyDetailsSelector<
PolicyDetailsState['artifacts']['policies']
> = (state) => state.artifacts.policies;
export const getTrustedAppsListOfAllPolicies: PolicyDetailsSelector<PolicyData[]> = createSelector(
getTrustedAppsPolicyListState,
(policyListState) => {
return getLastLoadedResourceState(policyListState)?.data.items ?? [];
}
);
export const getTrustedAppsAllPoliciesById: PolicyDetailsSelector<
Record<string, Immutable<PolicyData>>
> = createSelector(getTrustedAppsListOfAllPolicies, (allPolicies) => {
return allPolicies.reduce<Record<string, Immutable<PolicyData>>>((mapById, policy) => {
mapById[policy.id] = policy;
return mapById;
}, {}) as Immutable<Record<string, Immutable<PolicyData>>>;
});

View file

@ -6,13 +6,13 @@
*/
import {
GetTrustedListAppsResponse,
GetTrustedAppsListResponse,
PostTrustedAppCreateResponse,
} from '../../../../../common/endpoint/types';
import { createSampleTrustedApps, createSampleTrustedApp } from '../../trusted_apps/test_utils';
export const getMockListResponse: () => GetTrustedListAppsResponse = () => ({
export const getMockListResponse: () => GetTrustedAppsListResponse = () => ({
data: createSampleTrustedApps({}),
per_page: 100,
page: 1,

View file

@ -14,58 +14,41 @@ import {
PolicyData,
UIPolicyConfig,
PostTrustedAppCreateResponse,
GetTrustedListAppsResponse,
MaybeImmutable,
GetTrustedAppsListResponse,
} from '../../../../common/endpoint/types';
import { ServerApiError } from '../../../common/types';
import {
GetAgentStatusResponse,
GetOnePackagePolicyResponse,
GetPackagePoliciesResponse,
GetPackagesResponse,
UpdatePackagePolicyResponse,
} from '../../../../../fleet/common';
import { AsyncResourceState } from '../../state';
import { ImmutableMiddlewareAPI } from '../../../common/store';
import { AppAction } from '../../../common/store/actions';
import { TrustedAppsService } from '../trusted_apps/service';
export type PolicyDetailsStore = ImmutableMiddlewareAPI<PolicyDetailsState, AppAction>;
/**
* Function that runs Policy Details middleware
*/
export type MiddlewareRunner = (
coreStart: CoreStart,
store: ImmutableMiddlewareAPI<PolicyDetailsState, AppAction>,
context: MiddlewareRunnerContext,
store: PolicyDetailsStore,
action: MaybeImmutable<AppAction>
) => Promise<void>;
/**
* Policy list store state
*/
export interface PolicyListState {
/** Array of policy items */
policyItems: PolicyData[];
/** Information about the latest endpoint package */
endpointPackageInfo?: GetPackagesResponse['response'][0];
/** API error if loading data failed */
apiError?: ServerApiError;
/** total number of policies */
total: number;
/** Number of policies per page */
pageSize: number;
/** page number (zero based) */
pageIndex: number;
/** data is being retrieved from server */
isLoading: boolean;
/** current location information */
location?: Immutable<AppLocation>;
/** policy is being deleted */
isDeleting: boolean;
/** Deletion status */
deleteStatus?: boolean;
/** A summary of stats for the agents associated with a given Fleet Agent Policy */
agentStatusSummary?: GetAgentStatusResponse['results'];
export interface MiddlewareRunnerContext {
coreStart: CoreStart;
trustedAppsService: TrustedAppsService;
}
export type PolicyDetailsSelector<T = unknown> = (
state: Immutable<PolicyDetailsState>
) => Immutable<T>;
/**
* Policy details store state
*/
@ -90,6 +73,11 @@ export interface PolicyDetailsState {
license?: ILicense;
}
export interface PolicyAssignedTrustedApps {
location: PolicyDetailsArtifactsPageListLocationParams;
artifacts: GetTrustedAppsListResponse;
}
/**
* Policy artifacts store state
*/
@ -97,11 +85,15 @@ export interface PolicyArtifactsState {
/** artifacts location params */
location: PolicyDetailsArtifactsPageLocation;
/** A list of artifacts can be linked to the policy */
assignableList: AsyncResourceState<GetTrustedListAppsResponse>;
/** Represents if avaialble trusted apps entries exist, regardless of whether the list is showing results */
assignableList: AsyncResourceState<GetTrustedAppsListResponse>;
/** Represents if available trusted apps entries exist, regardless of whether the list is showing results */
assignableListEntriesExist: AsyncResourceState<boolean>;
/** A list of trusted apps going to be updated */
trustedAppsToUpdate: AsyncResourceState<PostTrustedAppCreateResponse[]>;
/** List of artifacts currently assigned to the policy (body specific and global) */
assignedList: AsyncResourceState<PolicyAssignedTrustedApps>;
/** A list of all available polices */
policies: AsyncResourceState<GetPolicyListResponse>;
}
export enum OS {
@ -110,13 +102,17 @@ export enum OS {
linux = 'linux',
}
export interface PolicyDetailsArtifactsPageLocation {
export interface PolicyDetailsArtifactsPageListLocationParams {
page_index: number;
page_size: number;
show?: 'list';
filter: string;
}
export interface PolicyDetailsArtifactsPageLocation
extends PolicyDetailsArtifactsPageListLocationParams {
show?: 'list';
}
/**
* Returns the keys of an object whose values meet a criteria.
* Ex) interface largeNestedObject = {

View file

@ -8,7 +8,7 @@
import React, { useMemo } from 'react';
import {
GetTrustedListAppsResponse,
GetTrustedAppsListResponse,
Immutable,
TrustedApp,
} from '../../../../../../../common/endpoint/types';
@ -16,7 +16,7 @@ import { Loader } from '../../../../../../common/components/loader';
import { ArtifactEntryCardMinified } from '../../../../../components/artifact_entry_card';
export interface PolicyArtifactsAssignableListProps {
artifacts: Immutable<GetTrustedListAppsResponse | undefined>; // Or other artifacts type like Event Filters or Endpoint Exceptions
artifacts: Immutable<GetTrustedAppsListResponse | undefined>; // Or other artifacts type like Event Filters or Endpoint Exceptions
selectedArtifactIds: string[];
selectedArtifactsUpdated: (id: string, selected: boolean) => void;
isListLoading: boolean;

View file

@ -12,8 +12,8 @@ import { EuiTabbedContent, EuiSpacer, EuiTabbedContentTab } from '@elastic/eui';
import { usePolicyDetailsSelector } from '../policy_hooks';
import {
isOnPolicyFormPage,
isOnPolicyTrustedAppsPage,
isOnPolicyFormView,
isOnPolicyTrustedAppsView,
policyIdFromParams,
} from '../../store/policy_details/selectors';
@ -23,8 +23,8 @@ import { getPolicyDetailPath, getPolicyTrustedAppsPath } from '../../../../commo
export const PolicyTabs = React.memo(() => {
const history = useHistory();
const isInSettingsTab = usePolicyDetailsSelector(isOnPolicyFormPage);
const isInTrustedAppsTab = usePolicyDetailsSelector(isOnPolicyTrustedAppsPage);
const isInSettingsTab = usePolicyDetailsSelector(isOnPolicyFormView);
const isInTrustedAppsTab = usePolicyDetailsSelector(isOnPolicyTrustedAppsView);
const policyId = usePolicyDetailsSelector(policyIdFromParams);
const tabs = useMemo(

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 { PolicyTrustedAppsLayout } from './layout';

View file

@ -17,6 +17,7 @@ import {
import { getCurrentArtifactsLocation } from '../../../store/policy_details/selectors';
import { usePolicyDetailsNavigateCallback, usePolicyDetailsSelector } from '../../policy_hooks';
import { PolicyTrustedAppsFlyout } from '../flyout';
import { PolicyTrustedAppsList } from '../list/policy_trusted_apps_list';
export const PolicyTrustedAppsLayout = React.memo(() => {
const location = usePolicyDetailsSelector(getCurrentArtifactsLocation);
@ -67,8 +68,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => {
color="transparent"
borderRadius="none"
>
{/* TODO: To be implemented */}
{'Policy trusted apps layout content'}
<PolicyTrustedAppsList />
</EuiPageContent>
{showListFlyout ? <PolicyTrustedAppsFlyout /> : null}
</div>

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 React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { EuiLoadingSpinner, EuiSpacer, EuiText, Pagination } from '@elastic/eui';
import { useHistory } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import {
ArtifactCardGrid,
ArtifactCardGridCardComponentProps,
ArtifactCardGridProps,
} from '../../../../../components/artifact_card_grid';
import { usePolicyDetailsSelector } from '../../policy_hooks';
import {
doesPolicyHaveTrustedApps,
getCurrentArtifactsLocation,
getPolicyTrustedAppList,
getPolicyTrustedAppsListPagination,
getTrustedAppsAllPoliciesById,
isPolicyTrustedAppListLoading,
policyIdFromParams,
} from '../../../store/policy_details/selectors';
import {
getPolicyDetailPath,
getPolicyDetailsArtifactsListPath,
getTrustedAppsListPath,
} from '../../../../../common/routing';
import { Immutable, TrustedApp } from '../../../../../../../common/endpoint/types';
import { useAppUrl } from '../../../../../../common/lib/kibana';
import { APP_ID } from '../../../../../../../common/constants';
import { ContextMenuItemNavByRouterProps } from '../../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router';
import { ArtifactEntryCollapsibleCardProps } from '../../../../../components/artifact_entry_card';
export const PolicyTrustedAppsList = memo(() => {
const history = useHistory();
const { getAppUrl } = useAppUrl();
const policyId = usePolicyDetailsSelector(policyIdFromParams);
const hasTrustedApps = usePolicyDetailsSelector(doesPolicyHaveTrustedApps);
const isLoading = usePolicyDetailsSelector(isPolicyTrustedAppListLoading);
const trustedAppItems = usePolicyDetailsSelector(getPolicyTrustedAppList);
const pagination = usePolicyDetailsSelector(getPolicyTrustedAppsListPagination);
const urlParams = usePolicyDetailsSelector(getCurrentArtifactsLocation);
const allPoliciesById = usePolicyDetailsSelector(getTrustedAppsAllPoliciesById);
const [isCardExpanded, setCardExpanded] = useState<Record<string, boolean>>({});
// TODO:PT show load errors if any
const handlePageChange = useCallback<ArtifactCardGridProps['onPageChange']>(
({ pageIndex, pageSize }) => {
history.push(
getPolicyDetailsArtifactsListPath(policyId, {
...urlParams,
// If user changed page size, then reset page index back to the first page
page_index: pageSize !== pagination.pageSize ? 0 : pageIndex,
page_size: pageSize,
})
);
},
[history, pagination.pageSize, policyId, urlParams]
);
const handleExpandCollapse = useCallback<ArtifactCardGridProps['onExpandCollapse']>(
({ expanded, collapsed }) => {
const newCardExpandedSettings: Record<string, boolean> = {};
for (const trustedApp of expanded) {
newCardExpandedSettings[trustedApp.id] = true;
}
for (const trustedApp of collapsed) {
newCardExpandedSettings[trustedApp.id] = false;
}
setCardExpanded(newCardExpandedSettings);
},
[]
);
const totalItemsCountLabel = useMemo<string>(() => {
return i18n.translate('xpack.securitySolution.endpoint.policy.trustedApps.list.totalCount', {
defaultMessage:
'Showing {totalItemsCount, plural, one {# trusted application} other {# trusted applications}}',
values: { totalItemsCount: pagination.totalItemCount },
});
}, [pagination.totalItemCount]);
const cardProps = useMemo<Map<Immutable<TrustedApp>, ArtifactCardGridCardComponentProps>>(() => {
const newCardProps = new Map();
for (const trustedApp of trustedAppItems) {
const viewUrlPath = getTrustedAppsListPath({ id: trustedApp.id, show: 'edit' });
const assignedPoliciesMenuItems: ArtifactEntryCollapsibleCardProps['policies'] =
trustedApp.effectScope.type === 'global'
? undefined
: trustedApp.effectScope.policies.reduce<
Required<ArtifactEntryCollapsibleCardProps>['policies']
>((byIdPolicies, trustedAppAssignedPolicyId) => {
if (!allPoliciesById[trustedAppAssignedPolicyId]) {
byIdPolicies[trustedAppAssignedPolicyId] = { children: trustedAppAssignedPolicyId };
return byIdPolicies;
}
const policyDetailsPath = getPolicyDetailPath(trustedAppAssignedPolicyId);
const thisPolicyMenuProps: ContextMenuItemNavByRouterProps = {
navigateAppId: APP_ID,
navigateOptions: {
path: policyDetailsPath,
},
href: getAppUrl({ path: policyDetailsPath }),
children: allPoliciesById[trustedAppAssignedPolicyId].name,
};
byIdPolicies[trustedAppAssignedPolicyId] = thisPolicyMenuProps;
return byIdPolicies;
}, {});
const thisTrustedAppCardProps: ArtifactCardGridCardComponentProps = {
expanded: Boolean(isCardExpanded[trustedApp.id]),
actions: [
{
icon: 'controlsHorizontal',
children: i18n.translate(
'xpack.securitySolution.endpoint.policy.trustedApps.list.viewAction',
{ defaultMessage: 'View full details' }
),
href: getAppUrl({ appId: APP_ID, path: viewUrlPath }),
navigateAppId: APP_ID,
navigateOptions: { path: viewUrlPath },
},
],
policies: assignedPoliciesMenuItems,
};
newCardProps.set(trustedApp, thisTrustedAppCardProps);
}
return newCardProps;
}, [allPoliciesById, getAppUrl, isCardExpanded, trustedAppItems]);
const provideCardProps = useCallback<Required<ArtifactCardGridProps>['cardComponentProps']>(
(item) => {
return cardProps.get(item as Immutable<TrustedApp>)!;
},
[cardProps]
);
// Anytime a new set of data (trusted apps) is retrieved, reset the card expand state
useEffect(() => {
setCardExpanded({});
}, [trustedAppItems]);
if (hasTrustedApps.loading) {
return (
<div>
<EuiLoadingSpinner className="essentialAnimation" size="xl" />
</div>
);
}
if (!hasTrustedApps.hasTrustedApps) {
// TODO: implement empty state (task #1645)
return <div>{'No trusted application'}</div>;
}
return (
<>
<EuiText color="subdued" size="xs" data-test-subj="policyDetailsTrustedAppsCount">
{totalItemsCountLabel}
</EuiText>
<EuiSpacer size="m" />
<ArtifactCardGrid
items={trustedAppItems}
onPageChange={handlePageChange}
onExpandCollapse={handleExpandCollapse}
cardComponentProps={provideCardProps}
loading={isLoading}
pagination={pagination as Pagination}
data-test-subj="policyTrustedAppsGrid"
/>
</>
);
});
PolicyTrustedAppsList.displayName = 'PolicyTrustedAppsList';

View file

@ -18,7 +18,7 @@ import {
import {
DeleteTrustedAppsRequestParams,
GetTrustedListAppsResponse,
GetTrustedAppsListResponse,
GetTrustedAppsListRequest,
PostTrustedAppCreateRequest,
PostTrustedAppCreateResponse,
@ -36,7 +36,7 @@ import { sendGetEndpointSpecificPackagePolicies } from '../../policy/store/servi
export interface TrustedAppsService {
getTrustedApp(params: GetOneTrustedAppRequestParams): Promise<GetOneTrustedAppResponse>;
getTrustedAppsList(request: GetTrustedAppsListRequest): Promise<GetTrustedListAppsResponse>;
getTrustedAppsList(request: GetTrustedAppsListRequest): Promise<GetTrustedAppsListResponse>;
deleteTrustedApp(request: DeleteTrustedAppsRequestParams): Promise<void>;
createTrustedApp(request: PostTrustedAppCreateRequest): Promise<PostTrustedAppCreateResponse>;
updateTrustedApp(
@ -58,7 +58,7 @@ export class TrustedAppsHttpService implements TrustedAppsService {
}
async getTrustedAppsList(request: GetTrustedAppsListRequest) {
return this.http.get<GetTrustedListAppsResponse>(TRUSTED_APPS_LIST_API, {
return this.http.get<GetTrustedAppsListResponse>(TRUSTED_APPS_LIST_API, {
query: request,
});
}

View file

@ -407,7 +407,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
class="body-content undefined"
>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -563,7 +563,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -790,7 +790,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -946,7 +946,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -1173,7 +1173,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -1329,7 +1329,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -1556,7 +1556,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -1712,7 +1712,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -1939,7 +1939,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -2095,7 +2095,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -2322,7 +2322,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -2478,7 +2478,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -2705,7 +2705,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -2861,7 +2861,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -3088,7 +3088,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -3244,7 +3244,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -3471,7 +3471,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -3627,7 +3627,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -3854,7 +3854,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -4010,7 +4010,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -4532,7 +4532,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
class="body-content undefined"
>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -4688,7 +4688,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -4915,7 +4915,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -5071,7 +5071,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -5298,7 +5298,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -5454,7 +5454,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -5681,7 +5681,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -5837,7 +5837,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -6064,7 +6064,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -6220,7 +6220,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -6447,7 +6447,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -6603,7 +6603,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -6830,7 +6830,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -6986,7 +6986,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -7213,7 +7213,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -7369,7 +7369,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -7596,7 +7596,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -7752,7 +7752,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -7979,7 +7979,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -8135,7 +8135,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -8614,7 +8614,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
class="body-content undefined"
>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -8770,7 +8770,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -8997,7 +8997,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -9153,7 +9153,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -9380,7 +9380,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -9536,7 +9536,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -9763,7 +9763,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -9919,7 +9919,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -10146,7 +10146,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -10302,7 +10302,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -10529,7 +10529,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -10685,7 +10685,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -10912,7 +10912,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -11068,7 +11068,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -11295,7 +11295,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -11451,7 +11451,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -11678,7 +11678,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -11834,7 +11834,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div
@ -12061,7 +12061,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard"
class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow euiPanel--hasBorder c2 c3 artifactEntryCard "
data-test-subj="trustedAppCard"
>
<div
@ -12217,7 +12217,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not
</div>
</div>
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
data-test-subj="trustedAppCard-subHeader"
>
<div

View file

@ -13,7 +13,7 @@ import { fireEvent } from '@testing-library/dom';
import { MiddlewareActionSpyHelper } from '../../../../common/store/test_utils';
import {
ConditionEntryField,
GetTrustedListAppsResponse,
GetTrustedAppsListResponse,
NewTrustedApp,
OperatingSystem,
PostTrustedAppCreateResponse,
@ -83,7 +83,7 @@ describe('When on the Trusted Apps Page', () => {
page: number = 1,
// eslint-disable-next-line @typescript-eslint/naming-convention
per_page: number = 20
): GetTrustedListAppsResponse => {
): GetTrustedAppsListResponse => {
return {
data: [getFakeTrustedApp()],
total: 50, // << Should be a value large enough to fulfill two pages
@ -683,7 +683,7 @@ describe('When on the Trusted Apps Page', () => {
});
describe('and there are no trusted apps', () => {
const releaseExistsResponse: jest.MockedFunction<() => Promise<GetTrustedListAppsResponse>> =
const releaseExistsResponse: jest.MockedFunction<() => Promise<GetTrustedAppsListResponse>> =
jest.fn(async () => {
return {
data: [],
@ -692,7 +692,7 @@ describe('When on the Trusted Apps Page', () => {
per_page: 1,
};
});
const releaseListResponse: jest.MockedFunction<() => Promise<GetTrustedListAppsResponse>> =
const releaseListResponse: jest.MockedFunction<() => Promise<GetTrustedAppsListResponse>> =
jest.fn(async () => {
return {
data: [],

View file

@ -10,11 +10,17 @@ import { ToolingLog } from '@kbn/dev-utils';
import { KbnClient } from '@kbn/test';
import bluebird from 'bluebird';
import { basename } from 'path';
import { AxiosResponse } from 'axios';
import { TRUSTED_APPS_CREATE_API, TRUSTED_APPS_LIST_API } from '../../../common/endpoint/constants';
import { TrustedApp } from '../../../common/endpoint/types';
import { TrustedAppGenerator } from '../../../common/endpoint/data_generators/trusted_app_generator';
import { indexFleetEndpointPolicy } from '../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { setupFleetForEndpoint } from '../../../common/endpoint/data_loaders/setup_fleet_for_endpoint';
import { GetPolicyListResponse } from '../../../public/management/pages/policy/types';
import {
PACKAGE_POLICY_API_ROUTES,
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
} from '../../../../fleet/common';
const defaultLogger = new ToolingLog({ level: 'info', writeTo: process.stdout });
const separator = '----------------------------------------';
@ -83,21 +89,25 @@ export const run: (options?: RunOptions) => Promise<TrustedApp[]> = async ({
}),
]);
// Setup a list of read endpoint policies and return a method to randomly select one
// Setup a list of real endpoint policies and return a method to randomly select one
const randomPolicyId: () => string = await (async () => {
const randomN = (max: number): number => Math.floor(Math.random() * max);
const policyIds: string[] = [];
const policyIds: string[] =
(await fetchEndpointPolicies(kbnClient)).data.items.map((policy) => policy.id) || [];
for (let i = 0, t = 5; i < t; i++) {
policyIds.push(
(
await indexFleetEndpointPolicy(
kbnClient,
`Policy for Trusted App assignment ${i + 1}`,
installedEndpointPackage.version
)
).integrationPolicies[0].id
);
// If the number of existing policies is less than 5, then create some more policies
if (policyIds.length < 5) {
for (let i = 0, t = 5 - policyIds.length; i < t; i++) {
policyIds.push(
(
await indexFleetEndpointPolicy(
kbnClient,
`Policy for Trusted App assignment ${i + 1}`,
installedEndpointPackage.version
)
).integrationPolicies[0].id
);
}
}
return () => policyIds[randomN(policyIds.length)];
@ -153,3 +163,16 @@ const createRunLogger = () => {
},
});
};
const fetchEndpointPolicies = (
kbnClient: KbnClient
): Promise<AxiosResponse<GetPolicyListResponse>> => {
return kbnClient.request<GetPolicyListResponse>({
method: 'GET',
path: PACKAGE_POLICY_API_ROUTES.LIST_PATTERN,
query: {
perPage: 100,
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
},
});
};

View file

@ -16,7 +16,7 @@ import {
GetOneTrustedAppResponse,
GetTrustedAppsListRequest,
GetTrustedAppsSummaryResponse,
GetTrustedListAppsResponse,
GetTrustedAppsListResponse,
PostTrustedAppCreateRequest,
PostTrustedAppCreateResponse,
PutTrustedAppUpdateRequest,
@ -124,7 +124,7 @@ export const getTrustedApp = async (
export const getTrustedAppsList = async (
exceptionsListClient: ExceptionListClient,
{ page, per_page: perPage, kuery }: GetTrustedAppsListRequest
): Promise<GetTrustedListAppsResponse> => {
): Promise<GetTrustedAppsListResponse> => {
// Ensure list is created if it does not exist
await exceptionsListClient.createTrustedAppsList();