Skip to content

Commit

Permalink
[Table list view] Integrate package into visualizations, maps, graphs (
Browse files Browse the repository at this point in the history
  • Loading branch information
sebelga authored Sep 14, 2022
1 parent 8eba3e6 commit b236f9c
Show file tree
Hide file tree
Showing 39 changed files with 407 additions and 1,777 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ packages/analytics/shippers/elastic_v3/browser @elastic/kibana-core
packages/analytics/shippers/elastic_v3/common @elastic/kibana-core
packages/analytics/shippers/elastic_v3/server @elastic/kibana-core
packages/analytics/shippers/fullstory @elastic/kibana-core
packages/content-management/table_list @elastic/shared-ux
packages/core/analytics/core-analytics-browser @elastic/kibana-core
packages/core/analytics/core-analytics-browser-internal @elastic/kibana-core
packages/core/analytics/core-analytics-browser-mocks @elastic/kibana-core
Expand Down
7 changes: 7 additions & 0 deletions packages/content-management/table_list/kibana.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "shared-common",
"id": "@kbn/content-management-table-list",
"owner": "@elastic/shared-ux",
"runtimeDeps": [],
"typeDeps": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
*/
import React from 'react';
import type { ComponentType } from 'react';
import { from } from 'rxjs';

import { TableListViewProvider, Services } from '../services';

export const getMockServices = (overrides?: Partial<Services>) => {
const services: Services = {
canEditAdvancedSettings: true,
getListingLimitSettingsUrl: () => 'http://elastic.co',
notifyError: () => undefined,
currentAppId$: from('mockedApp'),
navigateToUrl: () => undefined,
...overrides,
};

Expand Down
4 changes: 4 additions & 0 deletions packages/content-management/table_list/src/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { from } from 'rxjs';

import { Services } from './services';

/**
Expand All @@ -23,6 +25,8 @@ export const getStoryServices = (params: Params, action: ActionFn = () => {}) =>
notifyError: (title, text) => {
action('notifyError')({ title, text });
},
currentAppId$: from('mockedApp'),
navigateToUrl: () => undefined,
...params,
};

Expand Down
43 changes: 26 additions & 17 deletions packages/content-management/table_list/src/services.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import React, { FC, useContext, useMemo } from 'react';
import type { EuiTableFieldDataColumnType, SearchFilterConfig } from '@elastic/eui';
import type { Observable } from 'rxjs';
import type { FormattedRelative } from '@kbn/i18n-react';
import { RedirectAppLinksKibanaProvider } from '@kbn/shared-ux-link-redirect-app';

import { UserContentCommonSchema } from './table_list_view';

Expand Down Expand Up @@ -37,6 +38,8 @@ export interface Services {
canEditAdvancedSettings: boolean;
getListingLimitSettingsUrl: () => string;
notifyError: NotifyFn;
currentAppId$: Observable<string | undefined>;
navigateToUrl: (url: string) => Promise<void> | void;
searchQueryParser?: (searchQuery: string) => {
searchQuery: string;
references?: SavedObjectsFindOptionsReference[];
Expand Down Expand Up @@ -68,6 +71,8 @@ export interface TableListViewKibanaDependencies {
};
};
getUrlForApp: (app: string, options: { path: string }) => string;
currentAppId$: Observable<string | undefined>;
navigateToUrl: (url: string) => Promise<void> | void;
};
notifications: {
toasts: {
Expand Down Expand Up @@ -146,23 +151,27 @@ export const TableListViewKibanaProvider: FC<TableListViewKibanaDependencies> =
}, [savedObjectsTagging]);

return (
<TableListViewProvider
canEditAdvancedSettings={Boolean(core.application.capabilities.advancedSettings?.save)}
getListingLimitSettingsUrl={() =>
core.application.getUrlForApp('management', {
path: `/kibana/settings?query=savedObjects:listingLimit`,
})
}
notifyError={(title, text) => {
core.notifications.toasts.addDanger({ title: toMountPoint(title), text });
}}
getTagsColumnDefinition={savedObjectsTagging?.ui.getTableColumnDefinition}
getSearchBarFilters={getSearchBarFilters}
searchQueryParser={searchQueryParser}
DateFormatterComp={(props) => <FormattedRelative {...props} />}
>
{children}
</TableListViewProvider>
<RedirectAppLinksKibanaProvider coreStart={core}>
<TableListViewProvider
canEditAdvancedSettings={Boolean(core.application.capabilities.advancedSettings?.save)}
getListingLimitSettingsUrl={() =>
core.application.getUrlForApp('management', {
path: `/kibana/settings?query=savedObjects:listingLimit`,
})
}
notifyError={(title, text) => {
core.notifications.toasts.addDanger({ title: toMountPoint(title), text });
}}
getTagsColumnDefinition={savedObjectsTagging?.ui.getTableColumnDefinition}
getSearchBarFilters={getSearchBarFilters}
searchQueryParser={searchQueryParser}
DateFormatterComp={(props) => <FormattedRelative {...props} />}
currentAppId$={core.application.currentAppId$}
navigateToUrl={core.application.navigateToUrl}
>
{children}
</TableListViewProvider>
</RedirectAppLinksKibanaProvider>
);
};

Expand Down
193 changes: 124 additions & 69 deletions packages/content-management/table_list/src/table_list_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@
* Side Public License, v 1.
*/

import React, { useReducer, useCallback, useEffect, useRef, useMemo, ReactNode } from 'react';
import React, {
useReducer,
useCallback,
useEffect,
useRef,
useMemo,
ReactNode,
MouseEvent,
} from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import {
EuiBasicTableColumn,
Expand All @@ -24,6 +32,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { IHttpFetchError } from '@kbn/core-http-browser';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';

import { Table, ConfirmDeleteModal, ListingLimitWarning } from './components';
import { useServices } from './services';
Expand Down Expand Up @@ -53,7 +62,10 @@ export interface Props<T extends UserContentCommonSchema = UserContentCommonSche
searchQuery: string,
references?: SavedObjectsFindOptionsReference[]
): Promise<{ total: number; hits: T[] }>;
getDetailViewLink(entity: T): string;
/** Handler to set the item title "href" value. If it returns undefined there won't be a link for this item. */
getDetailViewLink?: (entity: T) => string | undefined;
/** Handler to execute when clicking the item title */
onClickTitle?: (item: T) => void;
createItem?(): void;
deleteItems?(items: T[]): Promise<void>;
editItem?(item: T): void;
Expand Down Expand Up @@ -103,9 +115,22 @@ function TableListViewComp<T extends UserContentCommonSchema>({
editItem,
deleteItems,
getDetailViewLink,
onClickTitle,
id = 'userContent',
children,
}: Props<T>) {
if (!getDetailViewLink && !onClickTitle) {
throw new Error(
`[TableListView] One o["getDetailViewLink" or "onClickTitle"] prop must be provided.`
);
}

if (getDetailViewLink && onClickTitle) {
throw new Error(
`[TableListView] Either "getDetailViewLink" or "onClickTitle" can be provided. Not both.`
);
}

const isMounted = useRef(false);
const fetchIdx = useRef(0);

Expand All @@ -116,12 +141,24 @@ function TableListViewComp<T extends UserContentCommonSchema>({
searchQueryParser,
notifyError,
DateFormatterComp,
navigateToUrl,
currentAppId$,
} = useServices();

const reducer = useMemo(() => {
return getReducer<T>({ DateFormatterComp });
}, [DateFormatterComp]);

const redirectAppLinksCoreStart = useMemo(
() => ({
application: {
navigateToUrl,
currentAppId$,
},
}),
[navigateToUrl, currentAppId$]
);

const [state, dispatch] = useReducer<(state: State<T>, action: Action<T>) => State<T>>(reducer, {
items: [],
totalItems: 0,
Expand All @@ -137,14 +174,37 @@ function TableListViewComp<T extends UserContentCommonSchema>({
defaultMessage: 'Title',
}),
sortable: true,
render: (field: keyof T, record: T) => (
<EuiLink
href={getDetailViewLink(record)}
data-test-subj={`${id}ListingTitleLink-${record.attributes.title.split(' ').join('-')}`}
>
{record.attributes.title}
</EuiLink>
),
render: (field: keyof T, record: T) => {
// The validation is handled at the top of the component
const href = getDetailViewLink ? getDetailViewLink(record) : undefined;

if (!href && !onClickTitle) {
// This item is not clickable
return <span>{record.attributes.title}</span>;
}

return (
<RedirectAppLinks coreStart={redirectAppLinksCoreStart}>
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
<EuiLink
href={getDetailViewLink ? getDetailViewLink(record) : undefined}
onClick={
onClickTitle
? (e: MouseEvent) => {
e.preventDefault();
onClickTitle(record);
}
: undefined
}
data-test-subj={`${id}ListingTitleLink-${record.attributes.title
.split(' ')
.join('-')}`}
>
{record.attributes.title}
</EuiLink>
</RedirectAppLinks>
);
},
},
{
field: 'attributes.description',
Expand Down Expand Up @@ -391,74 +451,69 @@ function TableListViewComp<T extends UserContentCommonSchema>({

if (!fetchError && hasNoItems) {
return (
<KibanaPageTemplate
data-test-subj={pageDataTestSubject}
pageBodyProps={{
'aria-labelledby': hasInitialFetchReturned ? headingId : undefined,
}}
isEmptyState={true}
>
{renderNoItemsMessage()}
<KibanaPageTemplate panelled isEmptyState={true} data-test-subj={pageDataTestSubject}>
<KibanaPageTemplate.Section
aria-labelledby={hasInitialFetchReturned ? headingId : undefined}
>
{renderNoItemsMessage()}
</KibanaPageTemplate.Section>
</KibanaPageTemplate>
);
}

return (
<KibanaPageTemplate
data-test-subj={pageDataTestSubject}
pageHeader={{
pageTitle: <span id={headingId}>{tableListTitle}</span>,
rightSideItems: [renderCreateButton() ?? <span />],
'data-test-subj': 'top-nav',
}}
pageBodyProps={{
'aria-labelledby': hasInitialFetchReturned ? headingId : undefined,
}}
>
{/* Any children passed to the component */}
{children}

{/* Too many items error */}
{showLimitError && (
<ListingLimitWarning
canEditAdvancedSettings={canEditAdvancedSettings}
advancedSettingsLink={getListingLimitSettingsUrl()}
entityNamePlural={entityNamePlural}
totalItems={totalItems}
listingLimit={listingLimit}
/>
)}

{/* Error while fetching items */}
{showFetchError && renderFetchError()}

{/* Table of items */}
<Table<T>
dispatch={dispatch}
items={items}
isFetchingItems={isFetchingItems}
searchQuery={searchQuery}
tableColumns={tableColumns}
tableSort={tableSort}
pagination={pagination}
selectedIds={selectedIds}
entityName={entityName}
entityNamePlural={entityNamePlural}
deleteItems={deleteItems}
tableCaption={tableListTitle}
<KibanaPageTemplate panelled data-test-subj={pageDataTestSubject}>
<KibanaPageTemplate.Header
pageTitle={<span id={headingId}>{tableListTitle}</span>}
rightSideItems={[renderCreateButton() ?? <span />]}
data-test-subj="top-nav"
/>

{/* Delete modal */}
{showDeleteModal && (
<ConfirmDeleteModal<T>
isDeletingItems={isDeletingItems}
<KibanaPageTemplate.Section aria-labelledby={hasInitialFetchReturned ? headingId : undefined}>
{/* Any children passed to the component */}
{children}

{/* Too many items error */}
{showLimitError && (
<ListingLimitWarning
canEditAdvancedSettings={canEditAdvancedSettings}
advancedSettingsLink={getListingLimitSettingsUrl()}
entityNamePlural={entityNamePlural}
totalItems={totalItems}
listingLimit={listingLimit}
/>
)}

{/* Error while fetching items */}
{showFetchError && renderFetchError()}

{/* Table of items */}
<Table<T>
dispatch={dispatch}
items={items}
isFetchingItems={isFetchingItems}
searchQuery={searchQuery}
tableColumns={tableColumns}
tableSort={tableSort}
pagination={pagination}
selectedIds={selectedIds}
entityName={entityName}
entityNamePlural={entityNamePlural}
items={selectedItems}
onConfirm={deleteSelectedItems}
onCancel={() => dispatch({ type: 'onCancelDeleteItems' })}
deleteItems={deleteItems}
tableCaption={tableListTitle}
/>
)}

{/* Delete modal */}
{showDeleteModal && (
<ConfirmDeleteModal<T>
isDeletingItems={isDeletingItems}
entityName={entityName}
entityNamePlural={entityNamePlural}
items={selectedItems}
onConfirm={deleteSelectedItems}
onCancel={() => dispatch({ type: 'onCancelDeleteItems' })}
/>
)}
</KibanaPageTemplate.Section>
</KibanaPageTemplate>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage';
interface DashboardSavedObjectUserContent extends UserContentCommonSchema {
attributes: {
title: string;
description?: string;
timeRestore: boolean;
};
}
Expand All @@ -61,6 +62,7 @@ const toTableListViewSavedObject = (
type: 'dashboard',
attributes: {
title: (savedObject.title as string) ?? '',
description: savedObject.description as string,
timeRestore: savedObject.timeRestore as boolean,
},
};
Expand Down
Loading

0 comments on commit b236f9c

Please sign in to comment.