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

[TableListView] Enhance tag filtering #142108

Merged
merged 49 commits into from
Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
0c46a5a
[Core] Update SO client to support "hasNoReference"
sebelga Sep 29, 2022
8f24ba7
Revert "Remove EuiHighlight component"
sebelga Sep 26, 2022
c77df00
Fix regex when highlighting
sebelga Sep 26, 2022
52ee513
Update tagging parse search query to return excluded tags
sebelga Sep 28, 2022
c69e66d
Add missing handler from SavedOjbectTaggingService
sebelga Sep 28, 2022
8210483
Keep in state Query object instead of query string
sebelga Sep 28, 2022
521196a
Handle tags filtering in search bar
sebelga Sep 28, 2022
5bc6a54
Fix storybook story
sebelga Sep 28, 2022
a20b56a
Fix TS issue
sebelga Sep 28, 2022
b926e97
Update Dashboard to pass "hasNoReference" tag list
sebelga Sep 29, 2022
a6e68d9
Revert merge conflict resolution
sebelga Oct 12, 2022
27d839d
Fix TS issue
sebelga Oct 13, 2022
b881f0e
Fix Dashboard fetch
sebelga Oct 13, 2022
87bb91d
Add tag filter panel component
sebelga Oct 14, 2022
b04a45e
Expose getTagList() hander from tagging plugin
sebelga Oct 14, 2022
e69d78d
Aggregate tag by saved object
sebelga Oct 14, 2022
158aea1
Fix TS issues
sebelga Oct 14, 2022
1d4fefc
Add badge to select option
sebelga Oct 14, 2022
08e2652
Add tag selection state to panel
sebelga Oct 17, 2022
6defc08
Add handler to clear tag filtering
sebelga Oct 17, 2022
fac3cef
Add link to navigate to tag management
sebelga Oct 18, 2022
bd27745
Update dashboard integration
sebelga Oct 18, 2022
d16d6d7
Mark tag id as optional
sebelga Oct 18, 2022
8889f8c
Fix TS issues
sebelga Oct 18, 2022
ba6a4fd
Add support for ctrl click
sebelga Oct 18, 2022
4ce73b8
Refactor ctrl + click detection
sebelga Oct 24, 2022
25f8640
Add tagRender prop to tag list
sebelga Oct 24, 2022
2fd634c
Add tagRender prop to tag list (2)
sebelga Oct 24, 2022
d81540c
Add ctrl click tag in table
sebelga Oct 24, 2022
72f04cb
Refactor Ctrl Click Detect comp
sebelga Nov 8, 2022
a014959
Add jest test to filter by tag from the table
sebelga Nov 8, 2022
807a1f6
Add jest test to filter by tag from the searchbar filter
sebelga Nov 8, 2022
4d621b9
Revert changes to query_params.ts
sebelga Nov 8, 2022
3e00d82
Fix TS, comments
sebelga Nov 8, 2022
0ce852a
Update visualize listing to exclude tags
sebelga Nov 8, 2022
96107d2
Update maps listing to exclude tags
sebelga Nov 8, 2022
718500d
Update so tagging jest tests
sebelga Nov 8, 2022
df910e4
Move Query initialization to TableListView
sebelga Nov 9, 2022
31ce836
Change ownership of tagging to the Global Exp team
sebelga Nov 9, 2022
4af51ca
Fix TS issue
sebelga Nov 9, 2022
b930a61
Fix query instance target
sebelga Nov 9, 2022
6fa45f0
Merge remote-tracking branch 'upstream/main' into table-list-view/tag…
sebelga Nov 9, 2022
e6165c9
Use command (MacOS) and ctrl (Windows) as modified key to exclude tags
sebelga Nov 10, 2022
b834091
Moving tag filter panel state to hook
sebelga Nov 10, 2022
3d814f8
Merge branch 'main' into table-list-view/tag-selector
sebelga Nov 10, 2022
2abc8af
Fix jest tests
sebelga Nov 14, 2022
8b376ef
Fix graphs to always return an array of refs
sebelga Nov 14, 2022
a6d37c0
Merge remote-tracking branch 'upstream/main' into table-list-view/tag…
sebelga Nov 14, 2022
3edd0bb
Rename isInTransition by isInUse
sebelga Nov 14, 2022
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
8 changes: 5 additions & 3 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@
/docs/settings/reporting-settings.asciidoc @elastic/kibana-global-experience
/docs/setup/configuring-reporting.asciidoc @elastic/kibana-global-experience

### Global Experience Tagging
/src/plugins/saved_objects_tagging_oss @elastic/kibana-global-experience
/x-pack/plugins/saved_objects_tagging/ @elastic/kibana-global-experience
/x-pack/test/saved_object_tagging/ @elastic/kibana-global-experience

### Kibana React (to be deprecated)
/src/plugins/kibana_react/ @elastic/kibana-global-experience
/src/plugins/kibana_react/public/code_editor @elastic/kibana-global-experience @elastic/kibana-presentation
Expand Down Expand Up @@ -302,7 +307,6 @@
# Core
/examples/hello_world/ @elastic/kibana-core
/src/core/ @elastic/kibana-core
/src/plugins/saved_objects_tagging_oss @elastic/kibana-core
/config/kibana.yml @elastic/kibana-core
/typings/ @elastic/kibana-core
/x-pack/plugins/global_search_providers @elastic/kibana-core
Expand All @@ -312,9 +316,7 @@
/x-pack/plugins/global_search/ @elastic/kibana-core
/x-pack/plugins/cloud/ @elastic/kibana-core
/x-pack/plugins/cloud_integrations/ @elastic/kibana-core
/x-pack/plugins/saved_objects_tagging/ @elastic/kibana-core
/x-pack/test/saved_objects_field_count/ @elastic/kibana-core
/x-pack/test/saved_object_tagging/ @elastic/kibana-core
/src/plugins/saved_objects_management/ @elastic/kibana-core
/src/plugins/advanced_settings/ @elastic/kibana-core
/x-pack/plugins/global_search_bar/ @elastic/kibana-core
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export const getMockServices = (overrides?: Partial<Services>) => {
currentAppId$: from('mockedApp'),
navigateToUrl: () => undefined,
TagList,
getTagList: () => [],
itemHasTags: () => true,
getTagManagementUrl: () => '',
getTagIdsFromReferences: () => [],
...overrides,
};
Expand Down
7 changes: 5 additions & 2 deletions packages/content-management/table_list/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import type { IHttpFetchError } from '@kbn/core-http-browser';
import type { CriteriaWithPagination, Direction } from '@elastic/eui';
import type { CriteriaWithPagination, Direction, Query } from '@elastic/eui';

import type { SortColumnField } from './components';

Expand Down Expand Up @@ -71,7 +71,10 @@ export interface ShowConfirmDeleteItemsModalAction {
/** Action to update the search bar query text */
export interface OnSearchQueryChangeAction {
type: 'onSearchQueryChange';
data: string;
data: {
query: Query;
text: string;
};
}

export type Action<T> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export { ConfirmDeleteModal } from './confirm_delete_modal';
export { ListingLimitWarning } from './listing_limit_warning';
export { ItemDetails } from './item_details';
export { TableSortSelect } from './table_sort_select';
export { TagFilterPanel } from './tag_filter_panel';

export type { SortColumnField } from './table_sort_select';
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
*/

import React, { useCallback, useMemo } from 'react';
import { EuiText, EuiLink, EuiTitle, EuiSpacer } from '@elastic/eui';
import { EuiText, EuiLink, EuiTitle, EuiSpacer, EuiHighlight } from '@elastic/eui';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';

import type { Tag } from '../types';
import { useServices } from '../services';
import type { UserContentCommonSchema, Props as TableListViewProps } from '../table_list_view';
import { TagBadge } from './tag_badge';

type InheritedProps<T extends UserContentCommonSchema> = Pick<
TableListViewProps<T>,
Expand All @@ -20,21 +22,23 @@ type InheritedProps<T extends UserContentCommonSchema> = Pick<
interface Props<T extends UserContentCommonSchema> extends InheritedProps<T> {
item: T;
searchTerm?: string;
onClickTag: (tag: Tag, isCtrlKey: boolean) => void;
}

/**
* Copied from https://stackoverflow.com/a/9310752
*/
// const escapeRegExp = (text: string) => {
// return text.replace(/[-\[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
// };
const escapeRegExp = (text: string) => {
return text.replace(/[-\[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
};

export function ItemDetails<T extends UserContentCommonSchema>({
id,
item,
searchTerm = '',
getDetailViewLink,
onClickTitle,
onClickTag,
}: Props<T>) {
const {
references,
Expand Down Expand Up @@ -79,7 +83,9 @@ export function ItemDetails<T extends UserContentCommonSchema>({
onClick={onClickTitleHandler}
data-test-subj={`${id}ListingTitleLink-${item.attributes.title.split(' ').join('-')}`}
>
{title}
<EuiHighlight highlightAll search={escapeRegExp(searchTerm)}>
{title}
</EuiHighlight>
</EuiLink>
</RedirectAppLinks>
);
Expand All @@ -90,6 +96,7 @@ export function ItemDetails<T extends UserContentCommonSchema>({
onClickTitle,
onClickTitleHandler,
redirectAppLinksCoreStart,
searchTerm,
title,
]);

Expand All @@ -100,13 +107,20 @@ export function ItemDetails<T extends UserContentCommonSchema>({
<EuiTitle size="xs">{renderTitle()}</EuiTitle>
{Boolean(description) && (
<EuiText size="s">
<p>{description!}</p>
<p>
<EuiHighlight highlightAll search={escapeRegExp(searchTerm)}>
{description!}
</EuiHighlight>
</p>
</EuiText>
)}
{hasTags && (
<>
<EuiSpacer size="s" />
<TagList references={references} />
<TagList
references={references}
tagRender={(tag) => <TagBadge key={tag.name} tag={tag} onClick={onClickTag} />}
/>
</>
)}
</div>
Expand Down
97 changes: 85 additions & 12 deletions packages/content-management/table_list/src/components/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
PropertySort,
SearchFilterConfig,
Direction,
Query,
Ast,
} from '@elastic/eui';

import { useServices } from '../services';
Expand All @@ -26,14 +28,22 @@ import type {
UserContentCommonSchema,
} from '../table_list_view';
import { TableSortSelect } from './table_sort_select';
import { TagFilterPanel } from './tag_filter_panel';
import { useTagFilterPanel } from './use_tag_filter_panel';
import type { Params as UseTagFilterPanelParams } from './use_tag_filter_panel';
import type { SortColumnField } from './table_sort_select';

type State<T extends UserContentCommonSchema> = Pick<
TableListViewState<T>,
'items' | 'selectedIds' | 'searchQuery' | 'tableSort' | 'pagination'
>;

interface Props<T extends UserContentCommonSchema> extends State<T> {
type TagManagementProps = Pick<
UseTagFilterPanelParams,
'addOrRemoveIncludeTagFilter' | 'addOrRemoveExcludeTagFilter' | 'tagsToTableItemMap'
>;

interface Props<T extends UserContentCommonSchema> extends State<T>, TagManagementProps {
dispatch: Dispatch<Action<T>>;
entityName: string;
entityNamePlural: string;
Expand All @@ -44,6 +54,7 @@ interface Props<T extends UserContentCommonSchema> extends State<T> {
deleteItems: TableListViewProps<T>['deleteItems'];
onSortChange: (column: SortColumnField, direction: Direction) => void;
onTableChange: (criteria: CriteriaWithPagination<T>) => void;
clearTagSelection: () => void;
}

export function Table<T extends UserContentCommonSchema>({
Expand All @@ -58,12 +69,16 @@ export function Table<T extends UserContentCommonSchema>({
hasUpdatedAtMetadata,
entityName,
entityNamePlural,
tagsToTableItemMap,
deleteItems,
tableCaption,
onTableChange,
onSortChange,
addOrRemoveExcludeTagFilter,
addOrRemoveIncludeTagFilter,
clearTagSelection,
}: Props<T>) {
const { getSearchBarFilters } = useServices();
const { getTagList } = useServices();

const renderToolsLeft = useCallback(() => {
if (!deleteItems || selectedIds.length === 0) {
Expand Down Expand Up @@ -97,8 +112,37 @@ export function Table<T extends UserContentCommonSchema>({
}
: undefined;

const searchFilters = useMemo(() => {
const tableSortSelectFilter: SearchFilterConfig = {
const {
isPopoverOpen,
isInUse,
closePopover,
onFilterButtonClick,
onSelectChange,
options,
totalActiveFilters,
} = useTagFilterPanel({
query: searchQuery.query,
getTagList,
tagsToTableItemMap,
addOrRemoveExcludeTagFilter,
addOrRemoveIncludeTagFilter,
});

const onSearchQueryChange = useCallback(
(arg: { query: Query | null; queryText: string }) => {
dispatch({
type: 'onSearchQueryChange',
data: {
query: arg.query ?? new Query(Ast.create([]), undefined, arg.queryText),
text: arg.queryText,
},
});
},
[dispatch]
);

const tableSortSelectFilter = useMemo<SearchFilterConfig>(() => {
return {
type: 'custom_component',
component: () => {
return (
Expand All @@ -110,25 +154,53 @@ export function Table<T extends UserContentCommonSchema>({
);
},
};
}, [hasUpdatedAtMetadata, onSortChange, tableSort]);

const tagFilterPanel = useMemo<SearchFilterConfig>(() => {
return {
type: 'custom_component',
component: () => {
return (
<TagFilterPanel
isPopoverOpen={isPopoverOpen}
isInUse={isInUse}
closePopover={closePopover}
options={options}
totalActiveFilters={totalActiveFilters}
onFilterButtonClick={onFilterButtonClick}
onSelectChange={onSelectChange}
clearTagSelection={clearTagSelection}
/>
);
},
};
}, [
isPopoverOpen,
isInUse,
closePopover,
options,
totalActiveFilters,
onFilterButtonClick,
onSelectChange,
clearTagSelection,
]);

return getSearchBarFilters
? [tableSortSelectFilter, ...getSearchBarFilters()]
: [tableSortSelectFilter];
}, [onSortChange, hasUpdatedAtMetadata, tableSort, getSearchBarFilters]);
const searchFilters = useMemo(() => {
return [tableSortSelectFilter, tagFilterPanel];
}, [tableSortSelectFilter, tagFilterPanel]);

const search = useMemo(() => {
return {
onChange: ({ queryText }: { queryText: string }) =>
dispatch({ type: 'onSearchQueryChange', data: queryText }),
onChange: onSearchQueryChange,
toolsLeft: renderToolsLeft(),
defaultQuery: searchQuery,
query: searchQuery.query ?? undefined,
box: {
incremental: true,
'data-test-subj': 'tableListSearchBox',
},
filters: searchFilters,
};
}, [dispatch, renderToolsLeft, searchFilters, searchQuery]);
}, [onSearchQueryChange, renderToolsLeft, searchFilters, searchQuery.query]);

const noItemsMessage = (
<FormattedMessage
Expand All @@ -148,6 +220,7 @@ export function Table<T extends UserContentCommonSchema>({
message={noItemsMessage}
selection={selection}
search={search}
executeQueryOptions={{ enabled: false }}
sorting={tableSort ? { sort: tableSort as PropertySort } : undefined}
onChange={onTableChange}
data-test-subj="itemsInMemTable"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React, { FC } from 'react';
import { EuiBadge } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import type { Tag } from '../types';

const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;

export interface Props {
tag: Tag;
onClick: (tag: Tag, withModifierKey: boolean) => void;
}

/**
* The badge representation of a Tag, which is the default display to be used for them.
*/
export const TagBadge: FC<Props> = ({ tag, onClick }) => {
return (
<EuiBadge
color={tag.color}
title={tag.description}
data-test-subj={`tag-${tag.id}`}
onClick={(e) => {
const withModifierKey = (isMac && e.metaKey) || (!isMac && e.ctrlKey);
onClick(tag, withModifierKey);
}}
onClickAriaLabel={i18n.translate('contentManagement.tableList.tagBadge.buttonLabel', {
defaultMessage: '{tagName} tag button.',
values: {
tagName: tag.name,
},
})}
>
{tag.name}
</EuiBadge>
);
};
Loading