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

Cleanup spaces plugin #91976

Merged
merged 27 commits into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fad2f42
Convert relative imports to absolute imports
jportner Feb 18, 2021
388206d
Convert more relative imports to absolute imports
jportner Feb 18, 2021
dc49b70
Temporarily remove `import type` directive
jportner Feb 18, 2021
25a9066
Sort imports
jportner Feb 19, 2021
97cc9bb
Apply `import type` directive
jportner Feb 19, 2021
4ad077f
Add lazy loading of reusable Spaces UI components
jportner Feb 19, 2021
f022c4c
Reduce bundle size
jportner Feb 22, 2021
59a921c
Fix jest test error
jportner Feb 22, 2021
6284843
Refactor CTS flyout
jportner Feb 22, 2021
0872427
Move summarizeCopyResult
jportner Feb 22, 2021
9341c0c
Split CTS flyout into a separate chunk
jportner Feb 22, 2021
0080228
Lazy load NavControlPopover
jportner Feb 22, 2021
f3126fd
Refactor SpaceAvatar
jportner Feb 22, 2021
9b6cf54
Lazy load SpaceAvatar
jportner Feb 22, 2021
c922c54
Remove dependency on @kbn/std package
jportner Feb 22, 2021
b8d93b0
Change CTS flyout to use SpacesContext
jportner Feb 22, 2021
6242d1a
Merge branch 'master' into pr/jportner/91976
jportner Feb 25, 2021
a21ca9a
Abstract out lazy-loading from reusable components
jportner Feb 25, 2021
032b5cb
Use error boundaries when lazy-loading components
jportner Feb 25, 2021
bebf3b2
Rename `getSpacesContext` to `getSpacesContextProvider`
jportner Feb 25, 2021
b792353
Fix type check for unit test
jportner Feb 25, 2021
181ddcb
Fix i18n error
jportner Feb 25, 2021
2612620
Remove unused parameter
jportner Feb 25, 2021
1b6554b
Merge branch 'master' into pr/jportner/91976
jportner Feb 25, 2021
1b6c5e5
PR review feedback
jportner Feb 25, 2021
38fb012
Fix jest unit tests
jportner Feb 25, 2021
99ab44c
Merge branch 'master' into cleanup-spaces-plugin
kibanamachine Mar 1, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,26 @@
import React, { useEffect, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import { get } from 'lodash';
import { Query } from '@elastic/eui';
import { EuiLoadingSpinner, Query } from '@elastic/eui';
import { parse } from 'query-string';
import { i18n } from '@kbn/i18n';
import { CoreStart, ChromeBreadcrumb } from 'src/core/public';
import type {
SpacesAvailableStartContract,
SpacesContextProps,
} from 'src/plugins/spaces_oss/public';
import { DataPublicPluginStart } from '../../../data/public';
import { SavedObjectsTaggingApi } from '../../../saved_objects_tagging_oss/public';
import type { SpacesAvailableStartContract } from '../../../spaces_oss/public';
import {
ISavedObjectsManagementServiceRegistry,
SavedObjectsManagementActionServiceStart,
SavedObjectsManagementColumnServiceStart,
} from '../services';
import { SavedObjectsTable } from './objects_table';

const EmptyFunctionComponent: React.FC = ({ children }) => <>{children}</>;
const getEmptyFunctionComponent = async () => ({
default: (({ children }) => <>{children}</>) as React.FC,
});

const SavedObjectsTablePage = ({
coreStart,
Expand Down Expand Up @@ -71,41 +76,46 @@ const SavedObjectsTablePage = ({
}, [setBreadcrumbs]);

const ContextWrapper = useMemo(
() => spacesApi?.ui.components.SpacesContext || EmptyFunctionComponent,
() =>
React.lazy<React.FC<SpacesContextProps>>(
spacesApi ? spacesApi.ui.components.getSpacesContext : getEmptyFunctionComponent
),
[spacesApi]
);

return (
<ContextWrapper>
<SavedObjectsTable
initialQuery={initialQuery}
allowedTypes={allowedTypes}
serviceRegistry={serviceRegistry}
actionRegistry={actionRegistry}
columnRegistry={columnRegistry}
taggingApi={taggingApi}
savedObjectsClient={coreStart.savedObjects.client}
indexPatterns={dataStart.indexPatterns}
search={dataStart.search}
http={coreStart.http}
overlays={coreStart.overlays}
notifications={coreStart.notifications}
applications={coreStart.application}
perPageConfig={itemsPerPage}
goInspectObject={(savedObject) => {
const { editUrl } = savedObject.meta;
if (editUrl) {
return coreStart.application.navigateToUrl(
coreStart.http.basePath.prepend(`/app${editUrl}`)
);
}
}}
canGoInApp={(savedObject) => {
const { inAppUrl } = savedObject.meta;
return inAppUrl ? Boolean(get(capabilities, inAppUrl.uiCapabilitiesPath)) : false;
}}
/>
</ContextWrapper>
<React.Suspense fallback={<EuiLoadingSpinner />}>
<ContextWrapper>
<SavedObjectsTable
initialQuery={initialQuery}
allowedTypes={allowedTypes}
serviceRegistry={serviceRegistry}
actionRegistry={actionRegistry}
columnRegistry={columnRegistry}
taggingApi={taggingApi}
savedObjectsClient={coreStart.savedObjects.client}
indexPatterns={dataStart.indexPatterns}
search={dataStart.search}
http={coreStart.http}
overlays={coreStart.overlays}
notifications={coreStart.notifications}
applications={coreStart.application}
perPageConfig={itemsPerPage}
goInspectObject={(savedObject) => {
const { editUrl } = savedObject.meta;
if (editUrl) {
return coreStart.application.navigateToUrl(
coreStart.http.basePath.prepend(`/app${editUrl}`)
);
}
}}
canGoInApp={(savedObject) => {
const { inAppUrl } = savedObject.meta;
return inAppUrl ? Boolean(get(capabilities, inAppUrl.uiCapabilitiesPath)) : false;
}}
/>
</ContextWrapper>
</React.Suspense>
);
};
// eslint-disable-next-line import/no-default-export
Expand Down
9 changes: 5 additions & 4 deletions src/plugins/spaces_oss/public/api.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ type SpacesApiUiComponentMock = jest.Mocked<SpacesApiUiComponent>;

const createApiUiComponentsMock = () => {
const mock: SpacesApiUiComponentMock = {
SpacesContext: jest.fn(),
ShareToSpaceFlyout: jest.fn(),
SpaceList: jest.fn(),
LegacyUrlConflict: jest.fn(),
getSpacesContext: jest.fn(),
getShareToSpaceFlyout: jest.fn(),
getSpaceList: jest.fn(),
getLegacyUrlConflict: jest.fn(),
getSpaceAvatar: jest.fn(),
};

return mock;
Expand Down
36 changes: 31 additions & 5 deletions src/plugins/spaces_oss/public/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,19 @@ export interface SpacesApi {
ui: SpacesApiUi;
}

/**
* Function that returns a promise for a lazy-loadable component.
*
* @public
*/
export type LazyComponentFn<T> = () => Promise<{ default: FunctionComponent<T> }>;

/**
* @public
*/
export interface SpacesApiUi {
/**
* {@link SpacesApiUiComponent | React components} to support the spaces feature.
* Lazy-loadable {@link SpacesApiUiComponent | React components} to support the spaces feature.
*/
components: SpacesApiUiComponent;
/**
Expand Down Expand Up @@ -62,13 +69,13 @@ export interface SpacesApiUiComponent {
/**
* Provides a context that is required to render some Spaces components.
*/
SpacesContext: FunctionComponent<SpacesContextProps>;
getSpacesContext: LazyComponentFn<SpacesContextProps>;
/**
* Displays a flyout to edit the spaces that an object is shared to.
*
* Note: must be rendered inside of a SpacesContext.
*/
ShareToSpaceFlyout: FunctionComponent<ShareToSpaceFlyoutProps>;
getShareToSpaceFlyout: LazyComponentFn<ShareToSpaceFlyoutProps>;
/**
* Displays a corresponding list of spaces for a given list of saved object namespaces. It shows up to five spaces (and an indicator for
* any number of spaces that the user is not authorized to see) by default. If more than five named spaces would be displayed, the extras
Expand All @@ -77,7 +84,7 @@ export interface SpacesApiUiComponent {
*
* Note: must be rendered inside of a SpacesContext.
*/
SpaceList: FunctionComponent<SpaceListProps>;
getSpaceList: LazyComponentFn<SpaceListProps>;
/**
* Displays a callout that needs to be used if a call to `SavedObjectsClient.resolve()` results in an `"conflict"` outcome, which
* indicates that the user has loaded the page which is associated directly with one object (A), *and* with a legacy URL that points to a
Expand All @@ -95,7 +102,11 @@ export interface SpacesApiUiComponent {
*
* New URL path: `#/workpad/workpad-e08b9bdb-ec14-4339-94c4-063bddfd610e/page/1`
*/
LegacyUrlConflict: FunctionComponent<LegacyUrlConflictProps>;
getLegacyUrlConflict: LazyComponentFn<LegacyUrlConflictProps>;
/**
* Displays an avatar for the given space.
*/
getSpaceAvatar: LazyComponentFn<SpaceAvatarProps>;
}

/**
Expand Down Expand Up @@ -251,3 +262,18 @@ export interface LegacyUrlConflictProps {
*/
otherObjectPath: string;
}

/**
* @public
*/
export interface SpaceAvatarProps {
space: Partial<Space>;
size?: 's' | 'm' | 'l' | 'xl';
className?: string;
/**
* When enabled, allows EUI to provide an aria-label for this component, which is announced on screen readers.
*
* Default value is true.
*/
announceSpaceName?: boolean;
}
2 changes: 2 additions & 0 deletions src/plugins/spaces_oss/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export {
} from './types';

export {
LazyComponentFn,
SpacesApi,
SpacesApiUi,
SpacesApiUiComponent,
Expand All @@ -24,6 +25,7 @@ export {
ShareToSpaceSavedObjectTarget,
SpaceListProps,
LegacyUrlConflictProps,
SpaceAvatarProps,
} from './api';

export const plugin = () => new SpacesOssPlugin();
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import React, { FC, useState } from 'react';

import { EuiButtonEmpty } from '@elastic/eui';
import { EuiButtonEmpty, EuiLoadingSpinner } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { ShareToSpaceFlyoutProps } from 'src/plugins/spaces_oss/public';
import {
Expand Down Expand Up @@ -66,7 +66,9 @@ export const JobSpacesList: FC<Props> = ({ spacesApi, spaceIds, jobId, jobType,
});
}

const { SpaceList, ShareToSpaceFlyout } = spacesApi.ui.components;
const LazySpaceList = React.lazy(spacesApi.ui.components.getSpaceList);
const LazyShareToSpaceFlyout = React.lazy(spacesApi.ui.components.getShareToSpaceFlyout);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dynamic imports using React.lazy should be created outside the render function, otherwise you're creating a new component with every render of JobSpacesList

Copy link
Contributor

@pgayvallet pgayvallet Feb 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't really anything 'outside' of render in a FC 😅 . But useMemo should be used around the React.lazy declaration, yea (as it was properly done in saved_objects_table_page.tsx)

Highly depends on the answers on #91976 (comment) though.

Copy link
Contributor Author

@jportner jportner Feb 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pushed some changes.

Wherever I could do so (inside the Spaces plugin) I pulled out the lazy loading to the top level.
Otherwise, I memoized these inside of function components.


const shareToSpaceFlyoutProps: ShareToSpaceFlyoutProps = {
savedObjectTarget: {
type: ML_SAVED_OBJECT_TYPE,
Expand All @@ -81,11 +83,11 @@ export const JobSpacesList: FC<Props> = ({ spacesApi, spaceIds, jobId, jobType,
};

return (
<>
<React.Suspense fallback={<EuiLoadingSpinner />}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suspense will throw an error if the lazy component cannot be loaded so you might want to wrap this in an error boundary and create a fallback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component is only rendered if the Spaces plugin is enabled and the Spaces API is available, and the getters to lazy load these components (getSpaceList, getShareToSpaceFlyout) are just promises to import the components that we are already guaranteed to have available. IMO we don't need to complicate this with an error boundary and fallback when we can rely on the Kibana platform's public contracts.

Copy link
Contributor Author

@jportner jportner Feb 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline -- this is necessary if the user runs into any network errors at load time 😅 I'll add wrappers for these that display a toast message so the whole page doesn't blow up.

Edit: done in 032b5cb.

<EuiButtonEmpty onClick={() => setShowFlyout(true)} style={{ height: 'auto' }}>
<SpaceList namespaces={spaceIds} displayLimit={0} behaviorContext="outside-space" />
<LazySpaceList namespaces={spaceIds} displayLimit={0} behaviorContext="outside-space" />
</EuiButtonEmpty>
{showFlyout && <ShareToSpaceFlyout {...shareToSpaceFlyoutProps} />}
</>
{showFlyout && <LazyShareToSpaceFlyout {...shareToSpaceFlyoutProps} />}
</React.Suspense>
);
};
Loading