Skip to content

Commit

Permalink
[7.x] [Composable template] Preview composite template (elastic#72598) (
Browse files Browse the repository at this point in the history
elastic#73191)

Co-authored-by: Jean-Louis Leysens <jloleysens@gmail.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
3 people authored Jul 24, 2020
1 parent d47ab97 commit 42b3a79
Show file tree
Hide file tree
Showing 85 changed files with 1,878 additions and 986 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React, {
createContext,
useContext,
useState,
useCallback,
useMemo,
useEffect,
useRef,
} from 'react';
import { EuiFlyout } from '@elastic/eui';

interface Context {
addContent: <P extends object = { [key: string]: any }>(content: Content<P>) => void;
removeContent: (contentId: string) => void;
closeFlyout: () => void;
}

interface Content<P extends object = { [key: string]: any }> {
id: string;
Component: React.FunctionComponent<P>;
props?: P;
flyoutProps?: { [key: string]: any };
cleanUpFunc?: () => void;
}

const FlyoutMultiContentContext = createContext<Context | undefined>(undefined);

const DEFAULT_FLYOUT_PROPS = {
'data-test-subj': 'flyout',
size: 'm' as 'm',
maxWidth: 500,
};

export const GlobalFlyoutProvider: React.FC = ({ children }) => {
const [showFlyout, setShowFlyout] = useState(false);
const [activeContent, setActiveContent] = useState<Content<any> | undefined>(undefined);

const { id, Component, props, flyoutProps } = activeContent ?? {};

const addContent: Context['addContent'] = useCallback((content) => {
setActiveContent((prev) => {
if (prev !== undefined) {
if (prev.id !== content.id && prev.cleanUpFunc) {
// Clean up anything from the content about to be removed
prev.cleanUpFunc();
}
}
return content;
});

setShowFlyout(true);
}, []);

const closeFlyout: Context['closeFlyout'] = useCallback(() => {
setActiveContent(undefined);
setShowFlyout(false);
}, []);

const removeContent: Context['removeContent'] = useCallback(
(contentId: string) => {
if (contentId === id) {
closeFlyout();
}
},
[id, closeFlyout]
);

const mergedFlyoutProps = useMemo(() => {
return {
...DEFAULT_FLYOUT_PROPS,
onClose: closeFlyout,
...flyoutProps,
};
}, [flyoutProps, closeFlyout]);

const context: Context = {
addContent,
removeContent,
closeFlyout,
};

const ContentFlyout = showFlyout && Component !== undefined ? Component : null;

return (
<FlyoutMultiContentContext.Provider value={context}>
<>
{children}
{ContentFlyout && (
<EuiFlyout {...mergedFlyoutProps}>
<ContentFlyout {...props} />
</EuiFlyout>
)}
</>
</FlyoutMultiContentContext.Provider>
);
};

export const useGlobalFlyout = () => {
const ctx = useContext(FlyoutMultiContentContext);

if (ctx === undefined) {
throw new Error('useGlobalFlyout must be used within a <GlobalFlyoutProvider />');
}

const isMounted = useRef(false);
/**
* A component can add one or multiple content to the flyout
* during its lifecycle. When it unmounts, we will remove
* all those content added to the flyout.
*/
const contents = useRef<Set<string> | undefined>(undefined);
const { removeContent, addContent: addContentToContext } = ctx;

useEffect(() => {
isMounted.current = true;

return () => {
isMounted.current = false;
};
}, []);

const getContents = useCallback(() => {
if (contents.current === undefined) {
contents.current = new Set();
}
return contents.current;
}, []);

const addContent: Context['addContent'] = useCallback(
(content) => {
getContents().add(content.id);
return addContentToContext(content);
},
[getContents, addContentToContext]
);

useEffect(() => {
return () => {
if (!isMounted.current) {
// When the component unmounts, remove all the content it has added to the flyout
Array.from(getContents()).forEach(removeContent);
}
};
}, [removeContent]);

return { ...ctx, addContent };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export { GlobalFlyoutProvider, useGlobalFlyout } from './global_flyout';
25 changes: 21 additions & 4 deletions src/plugins/es_ui_shared/public/forms/form_wizard/form_wizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ import {
} from './form_wizard_context';
import { FormWizardNav, NavTexts } from './form_wizard_nav';

interface Props<T extends object> extends ProviderProps<T> {
interface Props<T extends object, S extends string> extends ProviderProps<T> {
isSaving?: boolean;
apiError: JSX.Element | null;
texts?: Partial<NavTexts>;
rightContentNav?: JSX.Element | null | ((stepId: S) => JSX.Element | null);
}

export function FormWizard<T extends object = { [key: string]: any }>({
export function FormWizard<T extends object = { [key: string]: any }, S extends string = any>({
texts,
defaultActiveStep,
defaultValue,
Expand All @@ -43,7 +44,8 @@ export function FormWizard<T extends object = { [key: string]: any }>({
onSave,
onChange,
children,
}: Props<T>) {
rightContentNav,
}: Props<T, S>) {
return (
<FormWizardProvider<T>
defaultValue={defaultValue}
Expand All @@ -53,7 +55,14 @@ export function FormWizard<T extends object = { [key: string]: any }>({
defaultActiveStep={defaultActiveStep}
>
<FormWizardConsumer>
{({ activeStepIndex, lastStep, steps, isCurrentStepValid, navigateToStep }) => {
{({
activeStepIndex,
lastStep,
steps,
isCurrentStepValid,
navigateToStep,
activeStepId,
}) => {
const stepsRequiredArray = Object.values(steps).map(
(step) => Boolean(step.isRequired) && step.isComplete === false
);
Expand Down Expand Up @@ -95,6 +104,13 @@ export function FormWizard<T extends object = { [key: string]: any }>({
};
});

const getRightContentNav = () => {
if (typeof rightContentNav === 'function') {
return rightContentNav(activeStepId);
}
return rightContentNav;
};

const onBack = () => {
const prevStep = activeStepIndex - 1;
navigateToStep(prevStep);
Expand Down Expand Up @@ -129,6 +145,7 @@ export function FormWizard<T extends object = { [key: string]: any }>({
onBack={onBack}
onNext={onNext}
texts={texts}
getRightContent={getRightContentNav}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface Props {
isSaving?: boolean;
isStepValid?: boolean;
texts?: Partial<NavTexts>;
getRightContent?: () => JSX.Element | null | undefined;
}

export interface NavTexts {
Expand All @@ -53,6 +54,7 @@ export const FormWizardNav = ({
onBack,
onNext,
texts,
getRightContent,
}: Props) => {
const isLastStep = activeStepIndex === lastStep;
const labels = {
Expand All @@ -66,6 +68,8 @@ export const FormWizardNav = ({
: labels.save
: labels.next;

const rightContent = getRightContent !== undefined ? getRightContent() : undefined;

return (
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
Expand Down Expand Up @@ -100,6 +104,8 @@ export const FormWizardNav = ({
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>

{rightContent && <EuiFlexItem grow={false}>{rightContent}</EuiFlexItem>}
</EuiFlexGroup>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export function useMultiContent<T extends object>({
const activeContentData: Partial<T> = {};

for (const [id, _content] of Object.entries(contents.current)) {
if (validation.contents[id as keyof T]) {
if (validation.contents[id as keyof T] !== false) {
const contentData = (_content as Content).getData();

// Replace the getData() handler with the cached value
Expand Down Expand Up @@ -161,7 +161,7 @@ export function useMultiContent<T extends object>({
);

/**
* Validate the multi-content active content(s) in the DOM
* Validate the content(s) currently in the DOM
*/
const validate = useCallback(async () => {
if (Object.keys(contents.current).length === 0) {
Expand Down
23 changes: 23 additions & 0 deletions src/plugins/es_ui_shared/public/global_flyout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export {
GlobalFlyoutProvider,
useGlobalFlyout,
} from '../../__packages_do_not_import__/global_flyout';
3 changes: 2 additions & 1 deletion src/plugins/es_ui_shared/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import * as Forms from './forms';
import * as Monaco from './monaco';
import * as ace from './ace';
import * as GlobalFlyout from './global_flyout';

export { JsonEditor, OnJsonEditorUpdateHandler } from './components/json_editor';

Expand Down Expand Up @@ -65,7 +66,7 @@ export {
useAuthorizationContext,
} from './authorization';

export { Monaco, Forms, ace };
export { Monaco, Forms, ace, GlobalFlyout };

export { extractQueryParams } from './url';

Expand Down
6 changes: 5 additions & 1 deletion src/plugins/es_ui_shared/static/forms/helpers/serializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,13 @@ interface StripEmptyFieldsOptions {
* @param options An optional configuration object. By default recursive it turned on.
*/
export const stripEmptyFields = (
object: { [key: string]: any },
object?: { [key: string]: any },
options?: StripEmptyFieldsOptions
): { [key: string]: any } => {
if (object === undefined) {
return {};
}

const { types = ['string', 'object'], recursive = false } = options || {};

return Object.entries(object).reduce((acc, [key, value]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
]);
};

const setSimulateTemplateResponse = (response?: HttpResponse, error?: any) => {
const status = error ? error.status || 400 : 200;
const body = error ? JSON.stringify(error.body) : JSON.stringify(response);

server.respondWith('POST', `${API_BASE_PATH}/index_templates/simulate`, [
status,
{ 'Content-Type': 'application/json' },
body,
]);
};

return {
setLoadTemplatesResponse,
setLoadIndicesResponse,
Expand All @@ -102,6 +113,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
setLoadTemplateResponse,
setCreateTemplateResponse,
setUpdateTemplateResponse,
setSimulateTemplateResponse,
};
};

Expand Down
Loading

0 comments on commit 42b3a79

Please sign in to comment.