-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Cases] Refactor attach alert to new case flyout #125505
Changes from 15 commits
d7833bd
f9de000
fe21f1a
55e496a
e137ba6
4c580b9
c6c89aa
6392bf1
205728e
044279c
04988d6
6392bb3
416d81b
66a0a0d
13caeb6
4962ed8
7deb110
6cf2996
38b7f50
a256d8f
ff64286
9cf248d
8b159d2
1486a88
a297fbf
314cdf0
a5dddde
aa6e16c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This reducer will be only to handle the state for the CasesContext which will handle the flyout, modals and in general third integrations. It is not intended to be used for all cases plugin purposes. |
||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
academo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* 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 { CreateCaseFlyoutProps } from '../create/flyout'; | ||
|
||
export const initialCasesContextState = (): CasesContextState => { | ||
academo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return { | ||
createCaseFlyout: { | ||
isFlyoutOpen: false, | ||
}, | ||
}; | ||
}; | ||
|
||
export interface CasesContextState { | ||
createCaseFlyout: { | ||
isFlyoutOpen: boolean; | ||
props?: CreateCaseFlyoutProps; | ||
}; | ||
} | ||
|
||
export enum CasesContextStoreActionsList { | ||
OPEN_CREATE_CASE_FLYOUT, | ||
CLOSE_CREATE_CASE_FLYOUT, | ||
} | ||
export type CasesContextStoreAction = | ||
| { | ||
type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT; | ||
payload: CreateCaseFlyoutProps; | ||
} | ||
| { type: CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT }; | ||
|
||
export const casesContextReducer: React.Reducer<CasesContextState, CasesContextStoreAction> = ( | ||
state: CasesContextState, | ||
action: CasesContextStoreAction | ||
): CasesContextState => { | ||
switch (action.type) { | ||
case CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT: { | ||
return { ...state, createCaseFlyout: { isFlyoutOpen: true, props: action.payload } }; | ||
} | ||
case CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT: { | ||
return { ...state, createCaseFlyout: { isFlyoutOpen: false } }; | ||
} | ||
default: | ||
return state; | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the component that decides to show or not the flyout based on the state and eventually the select case modal too and any other integration the cases plugin would offer. |
||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import React from 'react'; | ||
import { getCreateCaseFlyoutLazyNoProvider } from '../../methods'; | ||
import { CasesContextState } from './cases_context_reducer'; | ||
|
||
export const CasesContextUI = ({ state }: { state: CasesContextState }) => { | ||
semd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return ( | ||
<> | ||
{state.createCaseFlyout.isFlyoutOpen && state.createCaseFlyout.props !== undefined | ||
? getCreateCaseFlyoutLazyNoProvider(state.createCaseFlyout.props) | ||
: null} | ||
</> | ||
); | ||
}; | ||
CasesContextUI.displayName = 'CasesContextUi'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,21 +5,38 @@ | |
* 2.0. | ||
*/ | ||
|
||
import React, { useState, useEffect } from 'react'; | ||
import React, { useState, useEffect, useReducer, useMemo } from 'react'; | ||
import { merge } from 'lodash'; | ||
import { CasesContextValue, CasesFeatures } from '../../../common/ui/types'; | ||
import { DEFAULT_FEATURES } from '../../../common/constants'; | ||
import { DEFAULT_BASE_PATH } from '../../common/navigation'; | ||
import { useApplication } from './use_application'; | ||
import { | ||
CasesContextStoreAction, | ||
casesContextReducer, | ||
CasesContextState, | ||
initialCasesContextState, | ||
} from './cases_context_reducer'; | ||
import { CasesContextFeatures, CasesFeatures } from '../../containers/types'; | ||
import { CasesContextUI } from './cases_context_ui'; | ||
|
||
export const CasesContext = React.createContext<CasesContextValue | undefined>(undefined); | ||
export interface CasesContextValue { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Brought from common types. |
||
owner: string[]; | ||
appId: string; | ||
appTitle: string; | ||
userCanCrud: boolean; | ||
basePath: string; | ||
features: CasesContextFeatures; | ||
state: CasesContextState; | ||
dispatch: (action: CasesContextStoreAction) => void; | ||
academo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
export interface CasesContextProps | ||
extends Omit<CasesContextValue, 'appId' | 'appTitle' | 'basePath' | 'features'> { | ||
export interface CasesContextProps extends Pick<CasesContextValue, 'owner' | 'userCanCrud'> { | ||
basePath?: string; | ||
features?: CasesFeatures; | ||
} | ||
|
||
export const CasesContext = React.createContext<CasesContextValue | undefined>(undefined); | ||
|
||
export interface CasesContextStateValue extends Omit<CasesContextValue, 'appId' | 'appTitle'> { | ||
appId?: string; | ||
appTitle?: string; | ||
|
@@ -30,16 +47,19 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ | |
value: { owner, userCanCrud, basePath = DEFAULT_BASE_PATH, features = {} }, | ||
}) => { | ||
const { appId, appTitle } = useApplication(); | ||
const [value, setValue] = useState<CasesContextStateValue>(() => ({ | ||
owner, | ||
userCanCrud, | ||
basePath, | ||
/** | ||
* The empty object at the beginning avoids the mutation | ||
* of the DEFAULT_FEATURES object | ||
*/ | ||
features: merge({}, DEFAULT_FEATURES, features), | ||
})); | ||
const [state, dispatch] = useReducer(casesContextReducer, initialCasesContextState()); | ||
const [baseValue, setBaseValue] = useState<Omit<CasesContextStateValue, 'state' | 'dispatch'>>( | ||
() => ({ | ||
owner, | ||
userCanCrud, | ||
basePath, | ||
/** | ||
* The empty object at the beginning avoids the mutation | ||
* of the DEFAULT_FEATURES object | ||
*/ | ||
features: merge({}, DEFAULT_FEATURES, features), | ||
}) | ||
); | ||
semd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* `userCanCrud` prop may change by the parent plugin. | ||
|
@@ -48,7 +68,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ | |
*/ | ||
useEffect(() => { | ||
if (appId && appTitle) { | ||
setValue((prev) => ({ | ||
setBaseValue((prev) => ({ | ||
...prev, | ||
appId, | ||
appTitle, | ||
|
@@ -57,12 +77,22 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ | |
} | ||
}, [appTitle, appId, userCanCrud]); | ||
|
||
const value = useMemo(() => { | ||
return { ...baseValue, state, dispatch }; | ||
}, [baseValue, state]); | ||
|
||
return isCasesContextValue(value) ? ( | ||
<CasesContext.Provider value={value}>{children}</CasesContext.Provider> | ||
<CasesContext.Provider value={value}> | ||
academo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<CasesContextUI state={value.state} /> | ||
semd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{children} | ||
</CasesContext.Provider> | ||
) : null; | ||
}; | ||
CasesProvider.displayName = 'CasesProvider'; | ||
|
||
function isCasesContextValue(value: CasesContextStateValue): value is CasesContextValue { | ||
return value.appId != null && value.appTitle != null && value.userCanCrud != null; | ||
} | ||
|
||
// eslint-disable-next-line import/no-default-export | ||
export default CasesProvider; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,8 +16,8 @@ import { UsePostComment } from '../../../containers/use_post_comment'; | |
|
||
export interface CreateCaseFlyoutProps { | ||
afterCaseCreated?: (theCase: Case, postComment: UsePostComment['postComment']) => Promise<void>; | ||
onClose: () => void; | ||
onSuccess: (theCase: Case) => Promise<void>; | ||
onClose?: () => void; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These attributes are now optional because third party plugins might decide to not need them. The idea is they could simply say: "Open the modal, attach this info to a case" and might or not care about the outcome of the operation. |
||
onSuccess?: (theCase: Case) => Promise<void>; | ||
attachments?: CreateCaseAttachment; | ||
} | ||
|
||
|
@@ -66,6 +66,8 @@ const FormWrapper = styled.div` | |
|
||
export const CreateCaseFlyout = React.memo<CreateCaseFlyoutProps>( | ||
({ afterCaseCreated, onClose, onSuccess, attachments }) => { | ||
const handleCancel = onClose || function () {}; | ||
const handleOnSuccess = onSuccess || async function () {}; | ||
return ( | ||
<> | ||
<GlobalStyle /> | ||
|
@@ -85,8 +87,8 @@ export const CreateCaseFlyout = React.memo<CreateCaseFlyoutProps>( | |
<CreateCaseForm | ||
afterCaseCreated={afterCaseCreated} | ||
attachments={attachments} | ||
onCancel={onClose} | ||
onSuccess={onSuccess} | ||
onCancel={handleCancel} | ||
onSuccess={handleOnSuccess} | ||
withSteps={false} | ||
/> | ||
</FormWrapper> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This hook is the direct integration with third party plugins. Plugins will load this hook and use it to open or close the flyout as required. |
||
* 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 { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer'; | ||
import { useCasesContext } from '../../cases_context/use_cases_context'; | ||
import { CreateCaseFlyoutProps } from './create_case_flyout'; | ||
|
||
export const useCasesAddToNewCasesFlyout = (props: CreateCaseFlyoutProps) => { | ||
const context = useCasesContext(); | ||
const closeFlyout = () => { | ||
academo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
context.dispatch({ | ||
type: CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT, | ||
}); | ||
}; | ||
const openFlyout = () => { | ||
context.dispatch({ | ||
type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, | ||
payload: { | ||
...props, | ||
onClose: () => { | ||
closeFlyout(); | ||
if (props.onClose) { | ||
return props.onClose(); | ||
} | ||
}, | ||
afterCaseCreated: async (...args) => { | ||
closeFlyout(); | ||
if (props.afterCaseCreated) { | ||
return props.afterCaseCreated(...args); | ||
} | ||
}, | ||
}, | ||
}); | ||
}; | ||
return { | ||
open: openFlyout, | ||
close: closeFlyout, | ||
}; | ||
}; | ||
|
||
export type UseCasesAddToNewCasesFlyout = typeof useCasesAddToNewCasesFlyout; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,6 +57,7 @@ const MySpinner = styled(EuiLoadingSpinner)` | |
`; | ||
export type SupportedCreateCaseAttachment = CommentRequestAlertType | CommentRequestUserType; | ||
export type CreateCaseAttachment = SupportedCreateCaseAttachment[]; | ||
export type CaseAttachments = SupportedCreateCaseAttachment[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a type to replace |
||
|
||
export interface CreateCaseFormFieldsProps { | ||
connectors: ActionConnector[]; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lazy component to load the CasesContext. The Plugins should wrap the components that will show the flyout or modal on this. |
||
* 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 { EuiLoadingSpinner } from '@elastic/eui'; | ||
import React, { lazy, ReactNode, Suspense } from 'react'; | ||
import { CasesContextProps } from '../components/cases_context'; | ||
|
||
export type GetCasesContextProps = CasesContextProps; | ||
|
||
const CasesProviderLazy: React.FC<{ value: GetCasesContextProps }> = lazy( | ||
() => import('../components/cases_context') | ||
); | ||
|
||
const CasesProviderLazyWrapper = ({ | ||
owner, | ||
userCanCrud, | ||
features, | ||
children, | ||
}: GetCasesContextProps & { children: ReactNode }) => { | ||
return ( | ||
<Suspense fallback={<EuiLoadingSpinner />}> | ||
<CasesProviderLazy value={{ owner, userCanCrud, features }}>{children}</CasesProviderLazy> | ||
</Suspense> | ||
); | ||
}; | ||
CasesProviderLazyWrapper.displayName = 'CasesProviderLazyWrapper'; | ||
|
||
export const getCasesContextLazy = () => { | ||
return CasesProviderLazyWrapper; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was moved out of here as it is not a common type for BE and FE but only FE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@academo Out of curiosity what does BE and FE stand for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BE = Backend, FE = Frontend.