From e7f537e502059072ba539683ab5a8a603955ae36 Mon Sep 17 00:00:00 2001 From: M-ZubairAhmed Date: Tue, 6 Feb 2024 12:14:44 +0000 Subject: [PATCH] [MM-56758] Enhance search implementation for Admin console (#26099) --- .../admin_console/admin_console.tsx | 38 ++++++----- .../__snapshots__/admin_sidebar.test.tsx.snap | 54 +++++++-------- .../admin_sidebar/admin_sidebar.test.tsx | 14 ++-- .../admin_sidebar/admin_sidebar.tsx | 18 ++--- .../components/admin_console/highlight.tsx | 49 -------------- .../admin_console/search_keyword_marking.tsx | 65 +++++++++++++++++++ .../__snapshots__/system_users.test.tsx.snap | 2 +- .../system_users/system_users.tsx | 2 +- 8 files changed, 130 insertions(+), 112 deletions(-) delete mode 100644 webapp/channels/src/components/admin_console/highlight.tsx create mode 100644 webapp/channels/src/components/admin_console/search_keyword_marking.tsx diff --git a/webapp/channels/src/components/admin_console/admin_console.tsx b/webapp/channels/src/components/admin_console/admin_console.tsx index 3b233305e9..f05e6b09d3 100644 --- a/webapp/channels/src/components/admin_console/admin_console.tsx +++ b/webapp/channels/src/components/admin_console/admin_console.tsx @@ -12,6 +12,7 @@ import type {Role} from '@mattermost/types/roles'; import type {ActionResult} from 'mattermost-redux/types/actions'; import SchemaAdminSettings from 'components/admin_console/schema_admin_settings'; +import SearchKeywordMarking from 'components/admin_console/search_keyword_marking'; import AnnouncementBarController from 'components/announcement_bar'; import BackstageNavbar from 'components/backstage/components/backstage_navbar'; import DiscardChangesModal from 'components/discard_changes_modal'; @@ -23,7 +24,6 @@ import {applyTheme, resetTheme} from 'utils/utils'; import {LhsItemType} from 'types/store/lhs'; import AdminSidebar from './admin_sidebar'; -import Highlight from './highlight'; import type {AdminDefinitionSubSection, AdminDefinitionSection} from './types'; import type {PropsFromRedux} from './index'; @@ -31,7 +31,7 @@ import type {PropsFromRedux} from './index'; export type Props = PropsFromRedux & RouteComponentProps; type State = { - filter: string; + search: string; } // not every page in the system console will need the license and config, but the vast majority will @@ -48,11 +48,11 @@ type ExtraProps = { isCurrentUserSystemAdmin: boolean; } -export default class AdminConsole extends React.PureComponent { +class AdminConsole extends React.PureComponent { public constructor(props: Props) { super(props); this.state = { - filter: '', + search: '', }; } @@ -73,8 +73,8 @@ export default class AdminConsole extends React.PureComponent { applyTheme(this.props.currentTheme); } - private onFilterChange = (filter: string) => { - this.setState({filter}); + private handleSearchChange = (search: string) => { + this.setState({search}); }; private mainRolesLoaded(roles: Record) { @@ -192,14 +192,6 @@ export default class AdminConsole extends React.PureComponent { ); } - const discardChangesModal: JSX.Element = ( - - ); - const extraProps: ExtraProps = { enterpriseReady: this.props.buildEnterpriseReady, license, @@ -212,23 +204,33 @@ export default class AdminConsole extends React.PureComponent { cloud: this.props.cloud, isCurrentUserSystemAdmin: this.props.isCurrentUserSystemAdmin, }; + return ( <> - +
- + {this.renderRoutes(extraProps)} - +
- {discardChangesModal} + ); } } + +export default AdminConsole; diff --git a/webapp/channels/src/components/admin_console/admin_sidebar/__snapshots__/admin_sidebar.test.tsx.snap b/webapp/channels/src/components/admin_console/admin_sidebar/__snapshots__/admin_sidebar.test.tsx.snap index e7cc7ee2b9..6c3850d38b 100644 --- a/webapp/channels/src/components/admin_console/admin_sidebar/__snapshots__/admin_sidebar.test.tsx.snap +++ b/webapp/channels/src/components/admin_console/admin_sidebar/__snapshots__/admin_sidebar.test.tsx.snap @@ -43,8 +43,8 @@ exports[`components/AdminSidebar Plugins should filter plugins 1`] = `
-
-
+
@@ -123,8 +123,8 @@ exports[`components/AdminSidebar Plugins should match snapshot 1`] = `
-
-
+
@@ -530,8 +530,8 @@ exports[`components/AdminSidebar should match snapshot 1`] = `
-
-
+
@@ -1224,8 +1224,8 @@ exports[`components/AdminSidebar should match snapshot with workspace optimizati
-
-
+
@@ -1918,13 +1918,13 @@ exports[`components/AdminSidebar should match snapshot, no access 1`] = `
-
    - +
@@ -1973,8 +1973,8 @@ exports[`components/AdminSidebar should match snapshot, not prevent the console
-
-
+
@@ -2667,8 +2667,8 @@ exports[`components/AdminSidebar should match snapshot, render plugins without a
-
-
+
@@ -3361,8 +3361,8 @@ exports[`components/AdminSidebar should match snapshot, with license (with all f
-
-
+
@@ -4217,8 +4217,8 @@ exports[`components/AdminSidebar should match snapshot, with license (without an
-
-
+
diff --git a/webapp/channels/src/components/admin_console/admin_sidebar/admin_sidebar.test.tsx b/webapp/channels/src/components/admin_console/admin_sidebar/admin_sidebar.test.tsx index 8356c0d607..d02f4c2d07 100644 --- a/webapp/channels/src/components/admin_console/admin_sidebar/admin_sidebar.test.tsx +++ b/webapp/channels/src/components/admin_console/admin_sidebar/admin_sidebar.test.tsx @@ -61,7 +61,7 @@ describe('components/AdminSidebar', () => { webapp: {bundle_path: 'webapp/dist/main.js'}, }, }, - onFilterChange: jest.fn(), + onSearchChange: jest.fn(), actions: { getPlugins: jest.fn(), }, @@ -175,7 +175,7 @@ describe('components/AdminSidebar', () => { webapp: {bundle_path: 'webapp/dist/main.js'}, }, }, - onFilterChange: jest.fn(), + onSearchChange: jest.fn(), actions: { getPlugins: jest.fn(), }, @@ -221,7 +221,7 @@ describe('components/AdminSidebar', () => { webapp: {bundle_path: 'webapp/dist/main.js'}, }, }, - onFilterChange: jest.fn(), + onSearchChange: jest.fn(), actions: { getPlugins: jest.fn(), }, @@ -269,7 +269,7 @@ describe('components/AdminSidebar', () => { webapp: {bundle_path: 'webapp/dist/main.js'}, }, }, - onFilterChange: jest.fn(), + onSearchChange: jest.fn(), actions: { getPlugins: jest.fn(), }, @@ -345,7 +345,7 @@ describe('components/AdminSidebar', () => { webapp: {bundle_path: 'webapp/dist/main.js'}, }, }, - onFilterChange: jest.fn(), + onSearchChange: jest.fn(), actions: { getPlugins: jest.fn(), }, @@ -379,7 +379,7 @@ describe('components/AdminSidebar', () => { plugins: { 'mattermost-autolink': samplePlugin1, }, - onFilterChange: jest.fn(), + onSearchChange: jest.fn(), actions: { getPlugins: jest.fn(), }, @@ -467,7 +467,7 @@ describe('components/AdminSidebar', () => { plugins: { 'mattermost-autolink': samplePlugin1, }, - onFilterChange: jest.fn(), + onSearchChange: jest.fn(), actions: { getPlugins: jest.fn(), }, diff --git a/webapp/channels/src/components/admin_console/admin_sidebar/admin_sidebar.tsx b/webapp/channels/src/components/admin_console/admin_sidebar/admin_sidebar.tsx index 81f80af137..3d4a8426cb 100644 --- a/webapp/channels/src/components/admin_console/admin_sidebar/admin_sidebar.tsx +++ b/webapp/channels/src/components/admin_console/admin_sidebar/admin_sidebar.tsx @@ -13,7 +13,7 @@ import type {PluginRedux} from '@mattermost/types/plugins'; import AdminSidebarCategory from 'components/admin_console/admin_sidebar/admin_sidebar_category'; import AdminSidebarSection from 'components/admin_console/admin_sidebar/admin_sidebar_section'; import AdminSidebarHeader from 'components/admin_console/admin_sidebar_header'; -import Highlight from 'components/admin_console/highlight'; +import SearchKeywordMarking from 'components/admin_console/search_keyword_marking'; import QuickInput from 'components/quick_input'; import SearchIcon from 'components/widgets/icons/search_icon'; @@ -27,7 +27,7 @@ import type {PropsFromRedux} from './index'; export interface Props extends PropsFromRedux { intl: IntlShape; - onFilterChange: (term: string) => void; + onSearchChange: (term: string) => void; } type State = { @@ -94,11 +94,11 @@ class AdminSidebar extends React.PureComponent { } } - onFilterChange = (e: React.ChangeEvent) => { + handleSearchChange = (e: React.ChangeEvent) => { const filter = e.target.value; if (filter === '') { this.setState({sections: null, filter}); - this.props.onFilterChange(filter); + this.props.onSearchChange(filter); return; } @@ -115,7 +115,7 @@ class AdminSidebar extends React.PureComponent { } const sections = this.idx.search(query); this.setState({sections, filter}); - this.props.onFilterChange(filter); + this.props.onSearchChange(filter); if (this.props.navigationBlocked) { return; @@ -286,7 +286,7 @@ class AdminSidebar extends React.PureComponent { handleClearFilter = () => { this.setState({sections: null, filter: ''}); - this.props.onFilterChange(''); + this.props.onSearchChange(''); }; render() { @@ -302,7 +302,7 @@ class AdminSidebar extends React.PureComponent { { renderView={renderScrollView} >
- +
    {this.renderRootMenu(this.props.adminDefinition)}
-
+
diff --git a/webapp/channels/src/components/admin_console/highlight.tsx b/webapp/channels/src/components/admin_console/highlight.tsx deleted file mode 100644 index e2b3a90f33..0000000000 --- a/webapp/channels/src/components/admin_console/highlight.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import debounce from 'lodash/debounce'; -import Mark from 'mark.js'; -import React from 'react'; - -type Props = { - filter: string; - children: React.ReactNode; -} - -export default class Highlight extends React.PureComponent { - private markInstance?: Mark; - private ref: React.RefObject; - - public constructor(props: Props) { - super(props); - this.ref = React.createRef(); - } - - private redrawHighlight = debounce(() => { - if (this.markInstance) { - this.markInstance.unmark(); - } - - if (!this.props.filter) { - return; - } - - if (!this.ref.current) { - return; - } - - // Is necesary to recreate the instances to get again the DOM elements after the re-render - this.markInstance = new Mark(this.ref.current); - this.markInstance.mark(this.props.filter, {accuracy: 'complementary'}); - }, 100, {leading: true, trailing: true}); - - public render() { - // Run on next frame - setTimeout(this.redrawHighlight, 0); - return ( -
- {this.props.children} -
- ); - } -} diff --git a/webapp/channels/src/components/admin_console/search_keyword_marking.tsx b/webapp/channels/src/components/admin_console/search_keyword_marking.tsx new file mode 100644 index 0000000000..52eacc908d --- /dev/null +++ b/webapp/channels/src/components/admin_console/search_keyword_marking.tsx @@ -0,0 +1,65 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import debounce from 'lodash/debounce'; +import Mark from 'mark.js'; +import React, {useEffect, useMemo, useRef} from 'react'; +import type {ReactNode} from 'react'; + +type Props = { + keyword?: string; + pathname?: string; + children: ReactNode; +} + +const DEBOUNCE_WAIT_TIME = 200; + +export default function SearchKeywordMarking({ + keyword = '', + pathname, + children, +}: Props) { + const containerRef = useRef(null); + const markJsRef = useRef(); + + function doMark(keyword: string, container: HTMLDivElement) { + markJsRef.current = new Mark(container); + markJsRef.current.mark(keyword, { + accuracy: 'complementary', + exclude: ['.ignore-marking *'], + }); + } + + const debouncedDoMark = useMemo(() => debounce((keywordToMark?: string, markJs?: Mark, container?: HTMLDivElement | null) => { + if (!keywordToMark || !container) { + return; + } + + if (markJs) { + // We need to mark again only after its 'done' callback is called + // if we dont then there is a possiblity of creating multiple marks in the same container + markJs.unmark({done: () => doMark(keywordToMark, container)}); + } else { + // If there's no previous instance, just create a new one + doMark(keywordToMark, container); + } + }, DEBOUNCE_WAIT_TIME), []); + + useEffect(() => { + debouncedDoMark(keyword, markJsRef.current, containerRef.current); + + return (() => { + debouncedDoMark.cancel(); + + if (markJsRef.current) { + markJsRef.current.unmark(); + } + }); + }, [keyword, pathname]); + + return ( +
+ {children} +
+ ); +} diff --git a/webapp/channels/src/components/admin_console/system_users/__snapshots__/system_users.test.tsx.snap b/webapp/channels/src/components/admin_console/system_users/__snapshots__/system_users.test.tsx.snap index 0143fca439..206c847c63 100644 --- a/webapp/channels/src/components/admin_console/system_users/__snapshots__/system_users.test.tsx.snap +++ b/webapp/channels/src/components/admin_console/system_users/__snapshots__/system_users.test.tsx.snap @@ -17,7 +17,7 @@ exports[`components/admin_console/system_users should match default snapshot 1`]
{ /> -
+