Skip to content
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] Attach framework registry #134744

Merged
merged 16 commits into from
Jun 30, 2022
2 changes: 2 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,8 @@
"@kbn/watcher-plugin/*": ["x-pack/plugins/watcher/*"],
"@kbn/alerting-fixture-plugin": ["x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts"],
"@kbn/alerting-fixture-plugin/*": ["x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/*"],
"@kbn/cases-fixture-plugin": ["x-pack/test/functional_with_es_ssl/fixtures/plugins/cases"],
"@kbn/cases-fixture-plugin/*": ["x-pack/test/functional_with_es_ssl/fixtures/plugins/cases/*"],
"@kbn/test-feature-usage-plugin": ["x-pack/test/licensing_plugin/plugins/test_feature_usage"],
"@kbn/test-feature-usage-plugin/*": ["x-pack/test/licensing_plugin/plugins/test_feature_usage/*"],
"@kbn/elasticsearch-client-xpack-plugin": ["x-pack/test/plugin_api_integration/plugins/elasticsearch_client"],
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/common/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
CasesStatusResponse,
CasesMetricsResponse,
CaseSeverity,
CommentResponseExternalReferenceType,
} from '../api';
import { SnakeToCamelCase } from '../types';

Expand Down Expand Up @@ -65,6 +66,7 @@ export type CaseViewRefreshPropInterface = null | {

export type Comment = SnakeToCamelCase<CommentResponse>;
export type AlertComment = SnakeToCamelCase<CommentResponseAlertsType>;
export type ExternalReferenceComment = SnakeToCamelCase<CommentResponseExternalReferenceType>;
export type CaseUserActions = SnakeToCamelCase<CaseUserActionResponse>;
export type CaseExternalService = SnakeToCamelCase<CaseExternalServiceBasic>;
export type Case = Omit<SnakeToCamelCase<CaseResponse>, 'comments'> & { comments: Comment[] };
Expand Down
31 changes: 22 additions & 9 deletions x-pack/plugins/cases/public/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { EuiThemeProvider as StyledComponentsThemeProvider } from '@kbn/kibana-react-plugin/common';
import { RenderAppProps } from './types';
import { CasesApp } from './components/app';
import { ExternalReferenceAttachmentTypeRegistry } from './client/attachment_framework/external_reference_registry';

export const renderApp = (deps: RenderAppProps) => {
const { mountParams } = deps;
Expand All @@ -31,15 +32,23 @@ export const renderApp = (deps: RenderAppProps) => {
};
};

const CasesAppWithContext = () => {
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
interface CasesAppWithContextProps {
externalReferenceAttachmentTypeRegistry: ExternalReferenceAttachmentTypeRegistry;
}

return (
<StyledComponentsThemeProvider darkMode={darkMode}>
<CasesApp />
</StyledComponentsThemeProvider>
);
};
const CasesAppWithContext: React.FC<CasesAppWithContextProps> = React.memo(
({ externalReferenceAttachmentTypeRegistry }) => {
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');

return (
<StyledComponentsThemeProvider darkMode={darkMode}>
<CasesApp
externalReferenceAttachmentTypeRegistry={externalReferenceAttachmentTypeRegistry}
/>
</StyledComponentsThemeProvider>
);
}
);

CasesAppWithContext.displayName = 'CasesAppWithContext';

Expand All @@ -60,7 +69,11 @@ export const App: React.FC<{ deps: RenderAppProps }> = ({ deps }) => {
}}
>
<Router history={history}>
<CasesAppWithContext />
<CasesAppWithContext
externalReferenceAttachmentTypeRegistry={
deps.externalReferenceAttachmentTypeRegistry
}
/>
</Router>
</KibanaContextProvider>
</KibanaThemeProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 { AttachmentTypeRegistry } from './registry';
import { ExternalReferenceAttachmentType } from './types';

export class ExternalReferenceAttachmentTypeRegistry extends AttachmentTypeRegistry<ExternalReferenceAttachmentType> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
cnasikas marked this conversation as resolved.
Show resolved Hide resolved
* 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 { i18n } from '@kbn/i18n';

interface BaseAttachmentType {
id: string;
}

export class AttachmentTypeRegistry<T extends BaseAttachmentType> {
private readonly attachmentTypes: Map<string, T> = new Map();

/**
* Returns if the attachment type registry has the given type registered
cnasikas marked this conversation as resolved.
Show resolved Hide resolved
*/
public has(id: string) {
return this.attachmentTypes.has(id);
}

/**
* Registers an attachment type to the type registry
*/
public register(attachmentType: T) {
if (this.has(attachmentType.id)) {
throw new Error(
i18n.translate('xpack.cases.typeRegistry.register.duplicateAttachmentTypeErrorMessage', {
defaultMessage: 'Attachment type "{id}" is already registered.',
values: {
id: attachmentType.id,
},
})
);
}

this.attachmentTypes.set(attachmentType.id, attachmentType);
}

/**
* Returns an attachment type, throw error if not registered
*/
public get(id: string): T {
const attachmentType = this.attachmentTypes.get(id);

if (!attachmentType) {
throw new Error(
i18n.translate('xpack.cases.typeRegistry.get.missingActionTypeErrorMessage', {
defaultMessage: 'Attachment type "{id}" is not registered.',
values: {
id,
},
})
);
}

return attachmentType;
}

public list() {
return Array.from(this.attachmentTypes).map(([id, attachmentType]) => attachmentType);
}
}
Original file line number Diff line number Diff line change
@@ -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 type React from 'react';
import { EuiCommentProps, IconType } from '@elastic/eui';
import { CommentRequestExternalReferenceType } from '../../../common/api';
import { Case } from '../../containers/types';

export interface ExternalReferenceAttachmentViewObject {
type?: EuiCommentProps['type'];
timelineIcon?: EuiCommentProps['timelineIcon'];
actions?: EuiCommentProps['actions'];
event?: EuiCommentProps['event'];
children?: React.LazyExoticComponent<React.FC>;
}

export interface ExternalReferenceAttachmentViewProps {
externalReferenceId: CommentRequestExternalReferenceType['externalReferenceId'];
externalReferenceMetadata: CommentRequestExternalReferenceType['externalReferenceMetadata'];
caseData: Pick<Case, 'id' | 'title'>;
}

export interface ExternalReferenceAttachmentType {
id: string;
icon: IconType;
displayName: string;
getAttachmentViewObject: (
props: ExternalReferenceAttachmentViewProps
) => ExternalReferenceAttachmentViewObject;
}

export interface AttachmentFramework {
registerExternalReference: (
externalReferenceAttachmentType: ExternalReferenceAttachmentType
) => void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ const AllCasesSelectorModalLazy: React.FC<AllCasesSelectorModalProps> = lazy(
() => import('../../components/all_cases/selector_modal')
);
export const getAllCasesSelectorModalLazy = ({
externalReferenceAttachmentTypeRegistry,
owner,
userCanCrud,
hiddenStatuses,
onRowClick,
onClose,
}: GetAllCasesSelectorModalProps) => (
<CasesProvider value={{ owner, userCanCrud }}>
<CasesProvider value={{ externalReferenceAttachmentTypeRegistry, owner, userCanCrud }}>
<Suspense fallback={<EuiLoadingSpinner />}>
<AllCasesSelectorModalLazy
hiddenStatuses={hiddenStatuses}
Expand Down
12 changes: 11 additions & 1 deletion x-pack/plugins/cases/public/client/ui/get_cases.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type GetCasesProps = CasesProps & CasesContextProps;
const CasesRoutesLazy: React.FC<CasesProps> = lazy(() => import('../../components/app/routes'));

export const getCasesLazy = ({
externalReferenceAttachmentTypeRegistry,
owner,
userCanCrud,
basePath,
Expand All @@ -28,7 +29,16 @@ export const getCasesLazy = ({
features,
releasePhase,
}: GetCasesProps) => (
<CasesProvider value={{ owner, userCanCrud, basePath, features, releasePhase }}>
<CasesProvider
value={{
externalReferenceAttachmentTypeRegistry,
owner,
userCanCrud,
basePath,
features,
releasePhase,
}}
>
<Suspense fallback={<EuiLoadingSpinner />}>
<CasesRoutesLazy
onComponentInitialized={onComponentInitialized}
Expand Down
27 changes: 24 additions & 3 deletions x-pack/plugins/cases/public/client/ui/get_cases_context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const CasesProviderLazy: React.FC<{ value: GetCasesContextProps }> = lazy(
);

const CasesProviderLazyWrapper = ({
externalReferenceAttachmentTypeRegistry,
owner,
userCanCrud,
features,
Expand All @@ -24,14 +25,34 @@ const CasesProviderLazyWrapper = ({
}: GetCasesContextProps & { children: ReactNode }) => {
return (
<Suspense fallback={<EuiLoadingSpinner />}>
<CasesProviderLazy value={{ owner, userCanCrud, features, releasePhase }}>
<CasesProviderLazy
value={{
externalReferenceAttachmentTypeRegistry,
owner,
userCanCrud,
features,
releasePhase,
}}
>
{children}
</CasesProviderLazy>
</Suspense>
);
};
CasesProviderLazyWrapper.displayName = 'CasesProviderLazyWrapper';

export const getCasesContextLazy = () => {
return CasesProviderLazyWrapper;
export const getCasesContextLazy = ({
externalReferenceAttachmentTypeRegistry,
}: Pick<GetCasesContextProps, 'externalReferenceAttachmentTypeRegistry'>) => {
// eslint-disable-next-line react/display-name
return (
props: Omit<GetCasesContextProps, 'externalReferenceAttachmentTypeRegistry'> & {
children: ReactNode;
}
) => (
<CasesProviderLazyWrapper
{...props}
externalReferenceAttachmentTypeRegistry={externalReferenceAttachmentTypeRegistry}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const CreateCaseFlyoutLazy: React.FC<CreateCaseFlyoutProps> = lazy(
() => import('../../components/create/flyout')
);
export const getCreateCaseFlyoutLazy = ({
externalReferenceAttachmentTypeRegistry,
owner,
userCanCrud,
features,
Expand All @@ -24,7 +25,7 @@ export const getCreateCaseFlyoutLazy = ({
onSuccess,
attachments,
}: GetCreateCaseFlyoutProps) => (
<CasesProvider value={{ owner, userCanCrud, features }}>
<CasesProvider value={{ externalReferenceAttachmentTypeRegistry, owner, userCanCrud, features }}>
<Suspense fallback={<EuiLoadingSpinner />}>
<CreateCaseFlyoutLazy
afterCaseCreated={afterCaseCreated}
Expand Down
9 changes: 7 additions & 2 deletions x-pack/plugins/cases/public/client/ui/get_recent_cases.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ export type GetRecentCasesProps = RecentCasesProps & CasesContextProps;
const RecentCasesLazy: React.FC<RecentCasesProps> = lazy(
() => import('../../components/recent_cases')
);
export const getRecentCasesLazy = ({ owner, userCanCrud, maxCasesToShow }: GetRecentCasesProps) => (
<CasesProvider value={{ owner, userCanCrud }}>
export const getRecentCasesLazy = ({
externalReferenceAttachmentTypeRegistry,
owner,
userCanCrud,
maxCasesToShow,
}: GetRecentCasesProps) => (
<CasesProvider value={{ externalReferenceAttachmentTypeRegistry, owner, userCanCrud }}>
<Suspense fallback={<EuiLoadingSpinner />}>
<RecentCasesLazy maxCasesToShow={maxCasesToShow} />
</Suspense>
Expand Down