[SIEM] [Case] Bulk status update, add comment avatar, id => title in breadcrumbs (#60410)
This commit is contained in:
parent
d5989e8baa
commit
0163a71d24
|
@ -38,18 +38,18 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] =
|
|||
data-test-subj="stat-item"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
data-test-subj="stat-item"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
data-test-subj="stat-item"
|
||||
>
|
||||
<InspectButtonContainer
|
||||
show={true}
|
||||
>
|
||||
<div
|
||||
className="sc-AykKF jvsAjF"
|
||||
className="sc-AykKI jTjOxk"
|
||||
>
|
||||
<EuiPanel>
|
||||
<div
|
||||
|
@ -152,10 +152,10 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] =
|
|||
key="stat-items-field-hosts"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
|
@ -167,17 +167,17 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] =
|
|||
>
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
>
|
||||
<StatValue>
|
||||
<EuiTitle
|
||||
className="sc-AykKH eFqnYS"
|
||||
className="sc-AykKK iEuJmh"
|
||||
>
|
||||
<p
|
||||
className="euiTitle euiTitle--medium sc-AykKH eFqnYS"
|
||||
className="euiTitle euiTitle--medium sc-AykKK iEuJmh"
|
||||
data-test-subj="stat-title"
|
||||
>
|
||||
<EmptyWrapper>
|
||||
|
@ -258,18 +258,18 @@ exports[`Stat Items Component disable charts it renders the default widget 2`] =
|
|||
data-test-subj="stat-item"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
data-test-subj="stat-item"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
data-test-subj="stat-item"
|
||||
>
|
||||
<InspectButtonContainer
|
||||
show={true}
|
||||
>
|
||||
<div
|
||||
className="sc-AykKF jvsAjF"
|
||||
className="sc-AykKI jTjOxk"
|
||||
>
|
||||
<EuiPanel>
|
||||
<div
|
||||
|
@ -372,10 +372,10 @@ exports[`Stat Items Component disable charts it renders the default widget 2`] =
|
|||
key="stat-items-field-hosts"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
|
@ -388,17 +388,17 @@ exports[`Stat Items Component disable charts it renders the default widget 2`] =
|
|||
0
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
>
|
||||
<StatValue>
|
||||
<EuiTitle
|
||||
className="sc-AykKH eFqnYS"
|
||||
className="sc-AykKK iEuJmh"
|
||||
>
|
||||
<p
|
||||
className="euiTitle euiTitle--medium sc-AykKH eFqnYS"
|
||||
className="euiTitle euiTitle--medium sc-AykKK iEuJmh"
|
||||
data-test-subj="stat-title"
|
||||
>
|
||||
<EmptyWrapper>
|
||||
|
@ -548,18 +548,18 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
|
|||
data-test-subj="stat-item"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
data-test-subj="stat-item"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
data-test-subj="stat-item"
|
||||
>
|
||||
<InspectButtonContainer
|
||||
show={true}
|
||||
>
|
||||
<div
|
||||
className="sc-AykKF jbBKkl"
|
||||
className="sc-AykKI gltyKM"
|
||||
>
|
||||
<EuiPanel>
|
||||
<div
|
||||
|
@ -662,10 +662,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
|
|||
key="stat-items-field-uniqueSourceIps"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
|
@ -679,11 +679,11 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
|
|||
grow={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
grow={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero sc-AykKG dLVTBE"
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero sc-AykKJ hHoWqT"
|
||||
>
|
||||
<EuiIcon
|
||||
color="#D36086"
|
||||
|
@ -703,17 +703,17 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
|
|||
</FlexItem>
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
>
|
||||
<StatValue>
|
||||
<EuiTitle
|
||||
className="sc-AykKH eFqnYS"
|
||||
className="sc-AykKK iEuJmh"
|
||||
>
|
||||
<p
|
||||
className="euiTitle euiTitle--medium sc-AykKH eFqnYS"
|
||||
className="euiTitle euiTitle--medium sc-AykKK iEuJmh"
|
||||
data-test-subj="stat-title"
|
||||
>
|
||||
1,714
|
||||
|
@ -734,10 +734,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
|
|||
key="stat-items-field-uniqueDestinationIps"
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
|
@ -751,11 +751,11 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
|
|||
grow={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
grow={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero sc-AykKG dLVTBE"
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero sc-AykKJ hHoWqT"
|
||||
>
|
||||
<EuiIcon
|
||||
color="#9170B8"
|
||||
|
@ -775,17 +775,17 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
|
|||
</FlexItem>
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
>
|
||||
<StatValue>
|
||||
<EuiTitle
|
||||
className="sc-AykKH eFqnYS"
|
||||
className="sc-AykKK iEuJmh"
|
||||
>
|
||||
<p
|
||||
className="euiTitle euiTitle--medium sc-AykKH eFqnYS"
|
||||
className="euiTitle euiTitle--medium sc-AykKK iEuJmh"
|
||||
data-test-subj="stat-title"
|
||||
>
|
||||
2,359
|
||||
|
@ -815,10 +815,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
|
|||
>
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
>
|
||||
<BarChart
|
||||
barChart={
|
||||
|
@ -874,10 +874,10 @@ exports[`Stat Items Component rendering kpis with charts it renders the default
|
|||
</FlexItem>
|
||||
<FlexItem>
|
||||
<EuiFlexItem
|
||||
className="sc-AykKG dLVTBE"
|
||||
className="sc-AykKJ hHoWqT"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem sc-AykKG dLVTBE"
|
||||
className="euiFlexItem sc-AykKJ hHoWqT"
|
||||
>
|
||||
<AreaChart
|
||||
areaChart={
|
||||
|
|
|
@ -15,7 +15,15 @@ import {
|
|||
User,
|
||||
} from '../../../../../../plugins/case/common/api';
|
||||
import { KibanaServices } from '../../lib/kibana';
|
||||
import { AllCases, Case, CasesStatus, Comment, FetchCasesProps, SortFieldCase } from './types';
|
||||
import {
|
||||
AllCases,
|
||||
BulkUpdateStatus,
|
||||
Case,
|
||||
CasesStatus,
|
||||
Comment,
|
||||
FetchCasesProps,
|
||||
SortFieldCase,
|
||||
} from './types';
|
||||
import { CASES_URL } from './constants';
|
||||
import {
|
||||
convertToCamelCase,
|
||||
|
@ -92,7 +100,7 @@ export const getCases = async ({
|
|||
};
|
||||
|
||||
export const postCase = async (newCase: CaseRequest): Promise<Case> => {
|
||||
const response = await KibanaServices.get().http.fetch<CaseResponse>(`${CASES_URL}`, {
|
||||
const response = await KibanaServices.get().http.fetch<CaseResponse>(CASES_URL, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(newCase),
|
||||
});
|
||||
|
@ -104,13 +112,21 @@ export const patchCase = async (
|
|||
updatedCase: Partial<CaseRequest>,
|
||||
version: string
|
||||
): Promise<Case[]> => {
|
||||
const response = await KibanaServices.get().http.fetch<CasesResponse>(`${CASES_URL}`, {
|
||||
const response = await KibanaServices.get().http.fetch<CasesResponse>(CASES_URL, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ cases: [{ ...updatedCase, id: caseId, version }] }),
|
||||
});
|
||||
return convertToCamelCase<CasesResponse, Case[]>(decodeCasesResponse(response));
|
||||
};
|
||||
|
||||
export const patchCasesStatus = async (cases: BulkUpdateStatus[]): Promise<Case[]> => {
|
||||
const response = await KibanaServices.get().http.fetch<CasesResponse>(CASES_URL, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ cases }),
|
||||
});
|
||||
return convertToCamelCase<CasesResponse, Case[]>(decodeCasesResponse(response));
|
||||
};
|
||||
|
||||
export const postComment = async (newComment: CommentRequest, caseId: string): Promise<Comment> => {
|
||||
const response = await KibanaServices.get().http.fetch<CommentResponse>(
|
||||
`${CASES_URL}/${caseId}/comments`,
|
||||
|
@ -139,7 +155,7 @@ export const patchComment = async (
|
|||
};
|
||||
|
||||
export const deleteCases = async (caseIds: string[]): Promise<boolean> => {
|
||||
const response = await KibanaServices.get().http.fetch<string>(`${CASES_URL}`, {
|
||||
const response = await KibanaServices.get().http.fetch<string>(CASES_URL, {
|
||||
method: 'DELETE',
|
||||
query: { ids: JSON.stringify(caseIds) },
|
||||
});
|
||||
|
|
|
@ -78,3 +78,9 @@ export interface FetchCasesProps {
|
|||
export interface ApiProps {
|
||||
signal: AbortSignal;
|
||||
}
|
||||
|
||||
export interface BulkUpdateStatus {
|
||||
status: string;
|
||||
id: string;
|
||||
version: string;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useCallback, useReducer } from 'react';
|
||||
import { errorToToaster, useStateToaster } from '../../components/toasters';
|
||||
import * as i18n from './translations';
|
||||
import { patchCasesStatus } from './api';
|
||||
import { BulkUpdateStatus, Case } from './types';
|
||||
|
||||
interface UpdateState {
|
||||
isUpdated: boolean;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
}
|
||||
type Action =
|
||||
| { type: 'FETCH_INIT' }
|
||||
| { type: 'FETCH_SUCCESS'; payload: boolean }
|
||||
| { type: 'FETCH_FAILURE' }
|
||||
| { type: 'RESET_IS_UPDATED' };
|
||||
|
||||
const dataFetchReducer = (state: UpdateState, action: Action): UpdateState => {
|
||||
switch (action.type) {
|
||||
case 'FETCH_INIT':
|
||||
return {
|
||||
...state,
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
};
|
||||
case 'FETCH_SUCCESS':
|
||||
return {
|
||||
...state,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
isUpdated: action.payload,
|
||||
};
|
||||
case 'FETCH_FAILURE':
|
||||
return {
|
||||
...state,
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
};
|
||||
case 'RESET_IS_UPDATED':
|
||||
return {
|
||||
...state,
|
||||
isUpdated: false,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
interface UseUpdateCase extends UpdateState {
|
||||
updateBulkStatus: (cases: Case[], status: string) => void;
|
||||
dispatchResetIsUpdated: () => void;
|
||||
}
|
||||
|
||||
export const useUpdateCases = (): UseUpdateCase => {
|
||||
const [state, dispatch] = useReducer(dataFetchReducer, {
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
isUpdated: false,
|
||||
});
|
||||
const [, dispatchToaster] = useStateToaster();
|
||||
|
||||
const dispatchUpdateCases = useCallback((cases: BulkUpdateStatus[]) => {
|
||||
let cancel = false;
|
||||
const patchData = async () => {
|
||||
try {
|
||||
dispatch({ type: 'FETCH_INIT' });
|
||||
await patchCasesStatus(cases);
|
||||
if (!cancel) {
|
||||
dispatch({ type: 'FETCH_SUCCESS', payload: true });
|
||||
}
|
||||
} catch (error) {
|
||||
if (!cancel) {
|
||||
errorToToaster({
|
||||
title: i18n.ERROR_TITLE,
|
||||
error: error.body && error.body.message ? new Error(error.body.message) : error,
|
||||
dispatchToaster,
|
||||
});
|
||||
dispatch({ type: 'FETCH_FAILURE' });
|
||||
}
|
||||
}
|
||||
};
|
||||
patchData();
|
||||
return () => {
|
||||
cancel = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const dispatchResetIsUpdated = useCallback(() => {
|
||||
dispatch({ type: 'RESET_IS_UPDATED' });
|
||||
}, []);
|
||||
|
||||
const updateBulkStatus = useCallback((cases: Case[], status: string) => {
|
||||
const updateCasesStatus: BulkUpdateStatus[] = cases.map(theCase => ({
|
||||
status,
|
||||
id: theCase.id,
|
||||
version: theCase.version,
|
||||
}));
|
||||
dispatchUpdateCases(updateCasesStatus);
|
||||
}, []);
|
||||
return { ...state, updateBulkStatus, dispatchResetIsUpdated };
|
||||
};
|
|
@ -5,19 +5,12 @@
|
|||
*/
|
||||
|
||||
import { npSetup, npStart } from 'ui/new_platform';
|
||||
import { PluginsSetup, PluginsStart } from 'ui/new_platform/new_platform';
|
||||
|
||||
import { PluginInitializerContext } from '../../../../../src/core/public';
|
||||
import { plugin } from './';
|
||||
import {
|
||||
TriggersAndActionsUIPublicPluginSetup,
|
||||
TriggersAndActionsUIPublicPluginStart,
|
||||
} from '../../../../plugins/triggers_actions_ui/public';
|
||||
import { SetupPlugins, StartPlugins } from './plugin';
|
||||
|
||||
const pluginInstance = plugin({} as PluginInitializerContext);
|
||||
|
||||
type myPluginsSetup = PluginsSetup & { triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup };
|
||||
type myPluginsStart = PluginsStart & { triggers_actions_ui: TriggersAndActionsUIPublicPluginStart };
|
||||
|
||||
pluginInstance.setup(npSetup.core, npSetup.plugins as myPluginsSetup);
|
||||
pluginInstance.start(npStart.core, npStart.plugins as myPluginsStart);
|
||||
pluginInstance.setup(npSetup.core, (npSetup.plugins as unknown) as SetupPlugins);
|
||||
pluginInstance.start(npStart.core, (npStart.plugins as unknown) as StartPlugins);
|
||||
|
|
|
@ -6,8 +6,13 @@
|
|||
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../common/constants';
|
||||
import { useUiSetting, useKibana } from './kibana_react';
|
||||
import { errorToToaster, useStateToaster } from '../../components/toasters';
|
||||
import { AuthenticatedUser } from '../../../../../../plugins/security/common/model';
|
||||
import { convertToCamelCase } from '../../containers/case/utils';
|
||||
|
||||
export const useDateFormat = (): string => useUiSetting<string>(DEFAULT_DATE_FORMAT);
|
||||
|
||||
|
@ -17,3 +22,62 @@ export const useTimeZone = (): string => {
|
|||
};
|
||||
|
||||
export const useBasePath = (): string => useKibana().services.http.basePath.get();
|
||||
|
||||
interface UserRealm {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface AuthenticatedElasticUser {
|
||||
username: string;
|
||||
email: string;
|
||||
fullName: string;
|
||||
roles: string[];
|
||||
enabled: boolean;
|
||||
metadata?: {
|
||||
_reserved: boolean;
|
||||
};
|
||||
authenticationRealm: UserRealm;
|
||||
lookupRealm: UserRealm;
|
||||
authenticationProvider: string;
|
||||
}
|
||||
|
||||
export const useCurrentUser = (): AuthenticatedElasticUser | null => {
|
||||
const [user, setUser] = useState<AuthenticatedElasticUser | null>(null);
|
||||
|
||||
const [, dispatchToaster] = useStateToaster();
|
||||
|
||||
const { security } = useKibana().services;
|
||||
|
||||
const fetchUser = useCallback(() => {
|
||||
let didCancel = false;
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await security.authc.getCurrentUser();
|
||||
if (!didCancel) {
|
||||
setUser(convertToCamelCase<AuthenticatedUser, AuthenticatedElasticUser>(response));
|
||||
}
|
||||
} catch (error) {
|
||||
if (!didCancel) {
|
||||
errorToToaster({
|
||||
title: i18n.translate('xpack.siem.getCurrentUser.Error', {
|
||||
defaultMessage: 'Error getting user',
|
||||
}),
|
||||
error: error.body && error.body.message ? new Error(error.body.message) : error,
|
||||
dispatchToaster,
|
||||
});
|
||||
setUser(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
return () => {
|
||||
didCancel = true;
|
||||
};
|
||||
}, [security]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUser();
|
||||
}, []);
|
||||
return user;
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@ import { UseGetCasesState } from '../../../../../containers/case/use_get_cases';
|
|||
export const useGetCasesMockState: UseGetCasesState = {
|
||||
data: {
|
||||
countClosedCases: 0,
|
||||
countOpenCases: 0,
|
||||
countOpenCases: 5,
|
||||
cases: [
|
||||
{
|
||||
closedAt: null,
|
||||
|
|
|
@ -10,35 +10,86 @@ import moment from 'moment-timezone';
|
|||
import { AllCases } from './';
|
||||
import { TestProviders } from '../../../../mock';
|
||||
import { useGetCasesMockState } from './__mock__';
|
||||
import * as apiHook from '../../../../containers/case/use_get_cases';
|
||||
import { act } from '@testing-library/react';
|
||||
import { wait } from '../../../../lib/helpers';
|
||||
import { useDeleteCases } from '../../../../containers/case/use_delete_cases';
|
||||
import { useGetCases } from '../../../../containers/case/use_get_cases';
|
||||
import { useGetCasesStatus } from '../../../../containers/case/use_get_cases_status';
|
||||
import { useUpdateCases } from '../../../../containers/case/use_bulk_update_case';
|
||||
jest.mock('../../../../containers/case/use_bulk_update_case');
|
||||
jest.mock('../../../../containers/case/use_delete_cases');
|
||||
jest.mock('../../../../containers/case/use_get_cases');
|
||||
jest.mock('../../../../containers/case/use_get_cases_status');
|
||||
const useDeleteCasesMock = useDeleteCases as jest.Mock;
|
||||
const useGetCasesMock = useGetCases as jest.Mock;
|
||||
const useGetCasesStatusMock = useGetCasesStatus as jest.Mock;
|
||||
const useUpdateCasesMock = useUpdateCases as jest.Mock;
|
||||
|
||||
describe('AllCases', () => {
|
||||
const dispatchResetIsDeleted = jest.fn();
|
||||
const dispatchResetIsUpdated = jest.fn();
|
||||
const dispatchUpdateCaseProperty = jest.fn();
|
||||
const handleOnDeleteConfirm = jest.fn();
|
||||
const handleToggleModal = jest.fn();
|
||||
const refetchCases = jest.fn();
|
||||
const setFilters = jest.fn();
|
||||
const setQueryParams = jest.fn();
|
||||
const setSelectedCases = jest.fn();
|
||||
const updateBulkStatus = jest.fn();
|
||||
const fetchCasesStatus = jest.fn();
|
||||
|
||||
const defaultGetCases = {
|
||||
...useGetCasesMockState,
|
||||
dispatchUpdateCaseProperty,
|
||||
refetchCases,
|
||||
setFilters,
|
||||
setQueryParams,
|
||||
setSelectedCases,
|
||||
};
|
||||
const defaultDeleteCases = {
|
||||
dispatchResetIsDeleted,
|
||||
handleOnDeleteConfirm,
|
||||
handleToggleModal,
|
||||
isDeleted: false,
|
||||
isDisplayConfirmDeleteModal: false,
|
||||
isLoading: false,
|
||||
};
|
||||
const defaultCasesStatus = {
|
||||
countClosedCases: 0,
|
||||
countOpenCases: 5,
|
||||
fetchCasesStatus,
|
||||
isError: false,
|
||||
isLoading: true,
|
||||
};
|
||||
const defaultUpdateCases = {
|
||||
isUpdated: false,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
dispatchResetIsUpdated,
|
||||
updateBulkStatus,
|
||||
};
|
||||
/* eslint-disable no-console */
|
||||
// Silence until enzyme fixed to use ReactTestUtils.act()
|
||||
const originalError = console.error;
|
||||
beforeAll(() => {
|
||||
console.error = jest.fn();
|
||||
});
|
||||
afterAll(() => {
|
||||
console.error = originalError;
|
||||
});
|
||||
/* eslint-enable no-console */
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.spyOn(apiHook, 'useGetCases').mockReturnValue({
|
||||
...useGetCasesMockState,
|
||||
dispatchUpdateCaseProperty,
|
||||
refetchCases,
|
||||
setFilters,
|
||||
setQueryParams,
|
||||
setSelectedCases,
|
||||
});
|
||||
useUpdateCasesMock.mockImplementation(() => defaultUpdateCases);
|
||||
useGetCasesMock.mockImplementation(() => defaultGetCases);
|
||||
useDeleteCasesMock.mockImplementation(() => defaultDeleteCases);
|
||||
useGetCasesStatusMock.mockImplementation(() => defaultCasesStatus);
|
||||
moment.tz.setDefault('UTC');
|
||||
});
|
||||
it('should render AllCases', async () => {
|
||||
it('should render AllCases', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<AllCases />
|
||||
</TestProviders>
|
||||
);
|
||||
await act(() => wait());
|
||||
expect(
|
||||
wrapper
|
||||
.find(`a[data-test-subj="case-details-link"]`)
|
||||
|
@ -76,13 +127,12 @@ describe('AllCases', () => {
|
|||
.text()
|
||||
).toEqual('Showing 10 cases');
|
||||
});
|
||||
it('should tableHeaderSortButton AllCases', async () => {
|
||||
it('should tableHeaderSortButton AllCases', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<AllCases />
|
||||
</TestProviders>
|
||||
);
|
||||
await act(() => wait());
|
||||
wrapper
|
||||
.find('[data-test-subj="tableHeaderSortButton"]')
|
||||
.first()
|
||||
|
@ -94,4 +144,139 @@ describe('AllCases', () => {
|
|||
sortOrder: 'asc',
|
||||
});
|
||||
});
|
||||
it('closes case when row action icon clicked', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<AllCases />
|
||||
</TestProviders>
|
||||
);
|
||||
wrapper
|
||||
.find('[data-test-subj="action-close"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
const firstCase = useGetCasesMockState.data.cases[0];
|
||||
expect(dispatchUpdateCaseProperty).toBeCalledWith({
|
||||
caseId: firstCase.id,
|
||||
updateKey: 'status',
|
||||
updateValue: 'closed',
|
||||
refetchCasesStatus: fetchCasesStatus,
|
||||
version: firstCase.version,
|
||||
});
|
||||
});
|
||||
it('Bulk delete', () => {
|
||||
useGetCasesMock.mockImplementation(() => ({
|
||||
...defaultGetCases,
|
||||
selectedCases: useGetCasesMockState.data.cases,
|
||||
}));
|
||||
useDeleteCasesMock
|
||||
.mockReturnValueOnce({
|
||||
...defaultDeleteCases,
|
||||
isDisplayConfirmDeleteModal: false,
|
||||
})
|
||||
.mockReturnValue({
|
||||
...defaultDeleteCases,
|
||||
isDisplayConfirmDeleteModal: true,
|
||||
});
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<AllCases />
|
||||
</TestProviders>
|
||||
);
|
||||
wrapper
|
||||
.find('[data-test-subj="case-table-bulk-actions"] button')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper
|
||||
.find('[data-test-subj="cases-bulk-delete-button"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
expect(handleToggleModal).toBeCalled();
|
||||
|
||||
wrapper
|
||||
.find(
|
||||
'[data-test-subj="confirm-delete-case-modal"] [data-test-subj="confirmModalConfirmButton"]'
|
||||
)
|
||||
.last()
|
||||
.simulate('click');
|
||||
expect(handleOnDeleteConfirm.mock.calls[0][0]).toStrictEqual(
|
||||
useGetCasesMockState.data.cases.map(theCase => theCase.id)
|
||||
);
|
||||
});
|
||||
it('Bulk close status update', () => {
|
||||
useGetCasesMock.mockImplementation(() => ({
|
||||
...defaultGetCases,
|
||||
selectedCases: useGetCasesMockState.data.cases,
|
||||
}));
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<AllCases />
|
||||
</TestProviders>
|
||||
);
|
||||
wrapper
|
||||
.find('[data-test-subj="case-table-bulk-actions"] button')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper
|
||||
.find('[data-test-subj="cases-bulk-close-button"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
expect(updateBulkStatus).toBeCalledWith(useGetCasesMockState.data.cases, 'closed');
|
||||
});
|
||||
it('Bulk open status update', () => {
|
||||
useGetCasesMock.mockImplementation(() => ({
|
||||
...defaultGetCases,
|
||||
selectedCases: useGetCasesMockState.data.cases,
|
||||
filterOptions: {
|
||||
...defaultGetCases.filterOptions,
|
||||
status: 'closed',
|
||||
},
|
||||
}));
|
||||
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<AllCases />
|
||||
</TestProviders>
|
||||
);
|
||||
wrapper
|
||||
.find('[data-test-subj="case-table-bulk-actions"] button')
|
||||
.first()
|
||||
.simulate('click');
|
||||
wrapper
|
||||
.find('[data-test-subj="cases-bulk-open-button"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
expect(updateBulkStatus).toBeCalledWith(useGetCasesMockState.data.cases, 'open');
|
||||
});
|
||||
it('isDeleted is true, refetch', () => {
|
||||
useDeleteCasesMock.mockImplementation(() => ({
|
||||
...defaultDeleteCases,
|
||||
isDeleted: true,
|
||||
}));
|
||||
|
||||
mount(
|
||||
<TestProviders>
|
||||
<AllCases />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(refetchCases).toBeCalled();
|
||||
expect(fetchCasesStatus).toBeCalled();
|
||||
expect(dispatchResetIsDeleted).toBeCalled();
|
||||
});
|
||||
it('isUpdated is true, refetch', () => {
|
||||
useUpdateCasesMock.mockImplementation(() => ({
|
||||
...defaultUpdateCases,
|
||||
isUpdated: true,
|
||||
}));
|
||||
|
||||
mount(
|
||||
<TestProviders>
|
||||
<AllCases />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(refetchCases).toBeCalled();
|
||||
expect(fetchCasesStatus).toBeCalled();
|
||||
expect(dispatchResetIsUpdated).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -43,6 +43,7 @@ import { OpenClosedStats } from '../open_closed_stats';
|
|||
|
||||
import { getActions } from './actions';
|
||||
import { CasesTableFilters } from './table_filters';
|
||||
import { useUpdateCases } from '../../../../containers/case/use_bulk_update_case';
|
||||
|
||||
const CONFIGURE_CASES_URL = getConfigureCasesUrl();
|
||||
const CREATE_CASE_URL = getCreateCaseUrl();
|
||||
|
@ -106,13 +107,20 @@ export const AllCases = React.memo(() => {
|
|||
isDisplayConfirmDeleteModal,
|
||||
} = useDeleteCases();
|
||||
|
||||
const { dispatchResetIsUpdated, isUpdated, updateBulkStatus } = useUpdateCases();
|
||||
|
||||
useEffect(() => {
|
||||
if (isDeleted) {
|
||||
refetchCases(filterOptions, queryParams);
|
||||
fetchCasesStatus();
|
||||
dispatchResetIsDeleted();
|
||||
}
|
||||
}, [isDeleted, filterOptions, queryParams]);
|
||||
if (isUpdated) {
|
||||
refetchCases(filterOptions, queryParams);
|
||||
fetchCasesStatus();
|
||||
dispatchResetIsUpdated();
|
||||
}
|
||||
}, [isDeleted, isUpdated, filterOptions, queryParams]);
|
||||
|
||||
const [deleteThisCase, setDeleteThisCase] = useState({
|
||||
title: '',
|
||||
|
@ -135,36 +143,38 @@ export const AllCases = React.memo(() => {
|
|||
[deleteBulk, deleteThisCase, isDisplayConfirmDeleteModal]
|
||||
);
|
||||
|
||||
const toggleDeleteModal = useCallback(
|
||||
(deleteCase: Case) => {
|
||||
handleToggleModal();
|
||||
setDeleteThisCase(deleteCase);
|
||||
},
|
||||
[isDisplayConfirmDeleteModal]
|
||||
);
|
||||
const toggleDeleteModal = useCallback((deleteCase: Case) => {
|
||||
handleToggleModal();
|
||||
setDeleteThisCase(deleteCase);
|
||||
}, []);
|
||||
|
||||
const toggleBulkDeleteModal = useCallback(
|
||||
(deleteCases: string[]) => {
|
||||
handleToggleModal();
|
||||
setDeleteBulk(deleteCases);
|
||||
const toggleBulkDeleteModal = useCallback((deleteCases: string[]) => {
|
||||
handleToggleModal();
|
||||
setDeleteBulk(deleteCases);
|
||||
}, []);
|
||||
|
||||
const handleUpdateCaseStatus = useCallback(
|
||||
(status: string) => {
|
||||
updateBulkStatus(selectedCases, status);
|
||||
},
|
||||
[isDisplayConfirmDeleteModal]
|
||||
[selectedCases]
|
||||
);
|
||||
|
||||
const selectedCaseIds = useMemo(
|
||||
(): string[] =>
|
||||
selectedCases.reduce((arr: string[], caseObj: Case) => [...arr, caseObj.id], []),
|
||||
(): string[] => selectedCases.map((caseObj: Case) => caseObj.id),
|
||||
[selectedCases]
|
||||
);
|
||||
|
||||
const getBulkItemsPopoverContent = useCallback(
|
||||
(closePopover: () => void) => (
|
||||
<EuiContextMenuPanel
|
||||
data-test-subj="cases-bulk-actions"
|
||||
items={getBulkItems({
|
||||
caseStatus: filterOptions.status,
|
||||
closePopover,
|
||||
deleteCasesAction: toggleBulkDeleteModal,
|
||||
selectedCaseIds,
|
||||
caseStatus: filterOptions.status,
|
||||
updateCaseStatus: handleUpdateCaseStatus,
|
||||
})}
|
||||
/>
|
||||
),
|
||||
|
@ -322,7 +332,7 @@ export const AllCases = React.memo(() => {
|
|||
</UtilityBar>
|
||||
<EuiBasicTable
|
||||
columns={memoizedGetCasesColumns}
|
||||
data-test-subj="all-cases-table"
|
||||
data-test-subj="cases-table"
|
||||
isSelectable
|
||||
itemId="id"
|
||||
items={data.cases}
|
||||
|
|
|
@ -12,8 +12,10 @@ export const NO_CASES = i18n.translate('xpack.siem.case.caseTable.noCases.title'
|
|||
defaultMessage: 'No Cases',
|
||||
});
|
||||
export const NO_CASES_BODY = i18n.translate('xpack.siem.case.caseTable.noCases.body', {
|
||||
defaultMessage: 'Create a new case to see it displayed in the case workflow table.',
|
||||
defaultMessage:
|
||||
'There are no cases to display. Please create a new case or change your filter settings above.',
|
||||
});
|
||||
|
||||
export const ADD_NEW_CASE = i18n.translate('xpack.siem.case.caseTable.addNewCase', {
|
||||
defaultMessage: 'Add New Case',
|
||||
});
|
||||
|
|
|
@ -9,47 +9,52 @@ import { EuiContextMenuItem } from '@elastic/eui';
|
|||
import * as i18n from './translations';
|
||||
|
||||
interface GetBulkItems {
|
||||
caseStatus: string;
|
||||
closePopover: () => void;
|
||||
deleteCasesAction: (cases: string[]) => void;
|
||||
selectedCaseIds: string[];
|
||||
caseStatus: string;
|
||||
updateCaseStatus: (status: string) => void;
|
||||
}
|
||||
|
||||
export const getBulkItems = ({
|
||||
deleteCasesAction,
|
||||
closePopover,
|
||||
caseStatus,
|
||||
closePopover,
|
||||
deleteCasesAction,
|
||||
selectedCaseIds,
|
||||
updateCaseStatus,
|
||||
}: GetBulkItems) => {
|
||||
return [
|
||||
caseStatus === 'open' ? (
|
||||
<EuiContextMenuItem
|
||||
data-test-subj="cases-bulk-close-button"
|
||||
key={i18n.BULK_ACTION_CLOSE_SELECTED}
|
||||
icon="magnet"
|
||||
disabled={true} // TO DO
|
||||
onClick={async () => {
|
||||
onClick={() => {
|
||||
closePopover();
|
||||
updateCaseStatus('closed');
|
||||
}}
|
||||
>
|
||||
{i18n.BULK_ACTION_CLOSE_SELECTED}
|
||||
</EuiContextMenuItem>
|
||||
) : (
|
||||
<EuiContextMenuItem
|
||||
data-test-subj="cases-bulk-open-button"
|
||||
key={i18n.BULK_ACTION_OPEN_SELECTED}
|
||||
icon="magnet"
|
||||
disabled={true} // TO DO
|
||||
onClick={() => {
|
||||
closePopover();
|
||||
updateCaseStatus('open');
|
||||
}}
|
||||
>
|
||||
{i18n.BULK_ACTION_OPEN_SELECTED}
|
||||
</EuiContextMenuItem>
|
||||
),
|
||||
<EuiContextMenuItem
|
||||
data-test-subj="cases-bulk-delete-button"
|
||||
key={i18n.BULK_ACTION_DELETE_SELECTED}
|
||||
icon="trash"
|
||||
disabled={selectedCaseIds.length === 0}
|
||||
onClick={async () => {
|
||||
onClick={() => {
|
||||
closePopover();
|
||||
deleteCasesAction(selectedCaseIds);
|
||||
}}
|
||||
|
|
|
@ -16,7 +16,7 @@ export const BULK_ACTION_CLOSE_SELECTED = i18n.translate(
|
|||
export const BULK_ACTION_OPEN_SELECTED = i18n.translate(
|
||||
'xpack.siem.case.caseTable.bulkActions.openSelectedTitle',
|
||||
{
|
||||
defaultMessage: 'Open selected',
|
||||
defaultMessage: 'Reopen selected',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { mount } from 'enzyme';
|
||||
import { CaseComponent } from './';
|
||||
import { caseProps, caseClosedProps, data, dataClosed } from './__mock__';
|
||||
|
@ -12,6 +13,27 @@ import { TestProviders } from '../../../../mock';
|
|||
import { useUpdateCase } from '../../../../containers/case/use_update_case';
|
||||
jest.mock('../../../../containers/case/use_update_case');
|
||||
const useUpdateCaseMock = useUpdateCase as jest.Mock;
|
||||
type Action = 'PUSH' | 'POP' | 'REPLACE';
|
||||
const pop: Action = 'POP';
|
||||
const location = {
|
||||
pathname: '/network',
|
||||
search: '',
|
||||
state: '',
|
||||
hash: '',
|
||||
};
|
||||
const mockHistory = {
|
||||
length: 2,
|
||||
location,
|
||||
action: pop,
|
||||
push: jest.fn(),
|
||||
replace: jest.fn(),
|
||||
go: jest.fn(),
|
||||
goBack: jest.fn(),
|
||||
goForward: jest.fn(),
|
||||
block: jest.fn(),
|
||||
createHref: jest.fn(),
|
||||
listen: jest.fn(),
|
||||
};
|
||||
|
||||
describe('CaseView ', () => {
|
||||
const updateCaseProperty = jest.fn();
|
||||
|
@ -42,7 +64,9 @@ describe('CaseView ', () => {
|
|||
it('should render CaseComponent', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<CaseComponent {...caseProps} />
|
||||
<Router history={mockHistory}>
|
||||
<CaseComponent {...caseProps} />
|
||||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(
|
||||
|
@ -83,6 +107,7 @@ describe('CaseView ', () => {
|
|||
.prop('raw')
|
||||
).toEqual(data.description);
|
||||
});
|
||||
|
||||
it('should show closed indicators in header when case is closed', () => {
|
||||
useUpdateCaseMock.mockImplementation(() => ({
|
||||
...defaultUpdateCaseState,
|
||||
|
@ -90,7 +115,9 @@ describe('CaseView ', () => {
|
|||
}));
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<CaseComponent {...caseClosedProps} />
|
||||
<Router history={mockHistory}>
|
||||
<CaseComponent {...caseClosedProps} />
|
||||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.contains(`[data-test-subj="case-view-createdAt"]`)).toBe(false);
|
||||
|
@ -111,7 +138,9 @@ describe('CaseView ', () => {
|
|||
it('should dispatch update state when button is toggled', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<CaseComponent {...caseProps} />
|
||||
<Router history={mockHistory}>
|
||||
<CaseComponent {...caseProps} />
|
||||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -128,7 +157,9 @@ describe('CaseView ', () => {
|
|||
it('should render comments', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<CaseComponent {...caseProps} />
|
||||
<Router history={mockHistory}>
|
||||
<CaseComponent {...caseProps} />
|
||||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(
|
||||
|
|
|
@ -23,6 +23,7 @@ import { getTypedPayload } from '../../../../containers/case/utils';
|
|||
import { WhitePageWrapper } from '../wrappers';
|
||||
import { useBasePath } from '../../../../lib/kibana';
|
||||
import { CaseStatus } from '../case_status';
|
||||
import { SpyRoute } from '../../../../utils/route/spy_routes';
|
||||
|
||||
interface Props {
|
||||
caseId: string;
|
||||
|
@ -93,6 +94,8 @@ export const CaseComponent = React.memo<CaseProps>(({ caseId, initialData }) =>
|
|||
const onSubmitTitle = useCallback(newTitle => onUpdateField('title', newTitle), [onUpdateField]);
|
||||
const toggleStatusCase = useCallback(status => onUpdateField('status', status), [onUpdateField]);
|
||||
|
||||
const spyState = useMemo(() => ({ caseTitle: caseData.title }), [caseData.title]);
|
||||
|
||||
const caseStatusData = useMemo(
|
||||
() =>
|
||||
caseData.status === 'open'
|
||||
|
@ -179,6 +182,7 @@ export const CaseComponent = React.memo<CaseProps>(({ caseId, initialData }) =>
|
|||
</EuiFlexGroup>
|
||||
</MyWrapper>
|
||||
</WhitePageWrapper>
|
||||
<SpyRoute state={spyState} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ import { useUpdateComment } from '../../../../containers/case/use_update_comment
|
|||
import { UserActionItem } from './user_action_item';
|
||||
import { UserActionMarkdown } from './user_action_markdown';
|
||||
import { AddComment } from '../add_comment';
|
||||
import { useCurrentUser } from '../../../../lib/kibana';
|
||||
|
||||
export interface UserActionTreeProps {
|
||||
data: Case;
|
||||
|
@ -20,14 +21,14 @@ export interface UserActionTreeProps {
|
|||
}
|
||||
|
||||
const DescriptionId = 'description';
|
||||
const NewId = 'newComent';
|
||||
const NewId = 'newComment';
|
||||
|
||||
export const UserActionTree = React.memo(
|
||||
({ data: caseData, onUpdateField, isLoadingDescription }: UserActionTreeProps) => {
|
||||
const { comments, isLoadingIds, updateComment, addPostedComment } = useUpdateComment(
|
||||
caseData.comments
|
||||
);
|
||||
|
||||
const currentUser = useCurrentUser();
|
||||
const [manageMarkdownEditIds, setManangeMardownEditIds] = useState<string[]>([]);
|
||||
|
||||
const handleManageMarkdownEditId = useCallback(
|
||||
|
@ -112,10 +113,10 @@ export const UserActionTree = React.memo(
|
|||
id={NewId}
|
||||
isEditable={true}
|
||||
isLoading={isLoadingIds.includes(NewId)}
|
||||
fullName="to be determined"
|
||||
fullName={currentUser != null ? currentUser.fullName : ''}
|
||||
markdown={MarkdownNewComment}
|
||||
onEdit={handleManageMarkdownEditId.bind(null, NewId)}
|
||||
userName="to be determined"
|
||||
userName={currentUser != null ? currentUser.username : ''}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiPanel } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import styled, { css } from 'styled-components';
|
||||
|
@ -48,6 +48,12 @@ const UserActionItemContainer = styled(EuiFlexGroup)`
|
|||
margin-right: ${theme.eui.euiSize};
|
||||
vertical-align: top;
|
||||
}
|
||||
.userAction_loadingAvatar {
|
||||
position: relative;
|
||||
margin-right: ${theme.eui.euiSizeXL};
|
||||
top: ${theme.eui.euiSizeM};
|
||||
left: ${theme.eui.euiSizeS};
|
||||
}
|
||||
.userAction__title {
|
||||
padding: ${theme.eui.euiSizeS} ${theme.eui.euiSizeL};
|
||||
background: ${theme.eui.euiColorLightestShade};
|
||||
|
@ -74,7 +80,11 @@ export const UserActionItem = ({
|
|||
}: UserActionItemProps) => (
|
||||
<UserActionItemContainer gutterSize={'none'}>
|
||||
<EuiFlexItem data-test-subj={`user-action-${id}-avatar`} grow={false}>
|
||||
<UserActionAvatar name={fullName ?? userName} />
|
||||
{fullName.length > 0 || userName.length > 0 ? (
|
||||
<UserActionAvatar name={fullName ?? userName} />
|
||||
) : (
|
||||
<EuiLoadingSpinner className="userAction_loadingAvatar" />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem data-test-subj={`user-action-${id}`}>
|
||||
{isEditable && markdown}
|
||||
|
|
|
@ -28,7 +28,7 @@ export const getBreadcrumbs = (params: RouteSpyState): Breadcrumb[] => {
|
|||
breadcrumb = [
|
||||
...breadcrumb,
|
||||
{
|
||||
text: params.detailName,
|
||||
text: params.state?.caseTitle ?? '',
|
||||
href: getCaseDetailsUrl(params.detailName),
|
||||
},
|
||||
];
|
||||
|
|
|
@ -27,21 +27,24 @@ import {
|
|||
TriggersAndActionsUIPublicPluginSetup,
|
||||
TriggersAndActionsUIPublicPluginStart,
|
||||
} from '../../../../plugins/triggers_actions_ui/public';
|
||||
import { SecurityPluginSetup } from '../../../../plugins/security/public';
|
||||
|
||||
export { AppMountParameters, CoreSetup, CoreStart, PluginInitializerContext };
|
||||
|
||||
export interface SetupPlugins {
|
||||
home: HomePublicPluginSetup;
|
||||
usageCollection: UsageCollectionSetup;
|
||||
security: SecurityPluginSetup;
|
||||
triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup;
|
||||
usageCollection: UsageCollectionSetup;
|
||||
}
|
||||
export interface StartPlugins {
|
||||
data: DataPublicPluginStart;
|
||||
embeddable: EmbeddableStart;
|
||||
inspector: InspectorStart;
|
||||
newsfeed?: NewsfeedStart;
|
||||
uiActions: UiActionsStart;
|
||||
security: SecurityPluginSetup;
|
||||
triggers_actions_ui: TriggersAndActionsUIPublicPluginStart;
|
||||
uiActions: UiActionsStart;
|
||||
}
|
||||
export type StartServices = CoreStart & StartPlugins;
|
||||
|
||||
|
@ -61,6 +64,8 @@ export class Plugin implements IPlugin<Setup, Start> {
|
|||
public setup(core: CoreSetup, plugins: SetupPlugins) {
|
||||
initTelemetry(plugins.usageCollection, this.id);
|
||||
|
||||
const security = plugins.security;
|
||||
|
||||
core.application.register({
|
||||
id: this.id,
|
||||
title: this.name,
|
||||
|
@ -69,8 +74,7 @@ export class Plugin implements IPlugin<Setup, Start> {
|
|||
const { renderApp } = await import('./app');
|
||||
|
||||
plugins.triggers_actions_ui.actionTypeRegistry.register(serviceNowActionType());
|
||||
|
||||
return renderApp(coreStart, startPlugins as StartPlugins, params);
|
||||
return renderApp(coreStart, { ...startPlugins, security } as StartPlugins, params);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -39,12 +39,13 @@ export const SpyRouteComponent = memo<SpyRouteProps & { location: H.Location }>(
|
|||
dispatch({
|
||||
type: 'updateRouteWithOutSearch',
|
||||
route: {
|
||||
pageName,
|
||||
detailName,
|
||||
tabName,
|
||||
pathName: pathname,
|
||||
history,
|
||||
flowTarget,
|
||||
history,
|
||||
pageName,
|
||||
pathName: pathname,
|
||||
state,
|
||||
tabName,
|
||||
},
|
||||
});
|
||||
setIsInitializing(false);
|
||||
|
@ -52,13 +53,14 @@ export const SpyRouteComponent = memo<SpyRouteProps & { location: H.Location }>(
|
|||
dispatch({
|
||||
type: 'updateRoute',
|
||||
route: {
|
||||
pageName,
|
||||
detailName,
|
||||
tabName,
|
||||
search,
|
||||
pathName: pathname,
|
||||
history,
|
||||
flowTarget,
|
||||
history,
|
||||
pageName,
|
||||
pathName: pathname,
|
||||
search,
|
||||
state,
|
||||
tabName,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -67,14 +69,14 @@ export const SpyRouteComponent = memo<SpyRouteProps & { location: H.Location }>(
|
|||
dispatch({
|
||||
type: 'updateRoute',
|
||||
route: {
|
||||
pageName,
|
||||
detailName,
|
||||
tabName,
|
||||
search,
|
||||
pathName: pathname,
|
||||
history,
|
||||
flowTarget,
|
||||
history,
|
||||
pageName,
|
||||
pathName: pathname,
|
||||
search,
|
||||
state,
|
||||
tabName,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue