diff --git a/webapp/channels/src/components/admin_console/server_logs/__snapshots__/logs.test.tsx.snap b/webapp/channels/src/components/admin_console/server_logs/__snapshots__/logs.test.tsx.snap
new file mode 100644
index 0000000000..f3556d56a1
--- /dev/null
+++ b/webapp/channels/src/components/admin_console/server_logs/__snapshots__/logs.test.tsx.snap
@@ -0,0 +1,510 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`components/admin_console/server_logs/Logs should display the logs correctly after loading 1`] = `
+
+
+
+
+
+
+
+
+ To look up users by User ID or Token ID, go to User Management > Users and paste the ID into the search filter.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ timestamp 1
+
+
+
+
+
+ info
+
+
+
+
+ msg 1
+
+
+
+
+ caller 1
+
+
+
+
+
+
+
+
+
+
+ timestamp 2
+
+
+
+
+
+ info
+
+
+
+
+ msg 2
+
+
+
+
+ caller 2
+
+
+
+
+
+
+
+
+
+
+ filtered
+
+
+
+
+
+ info
+
+
+
+
+ filtered message
+
+
+
+
+ filtered
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/webapp/channels/src/components/admin_console/server_logs/logs.test.tsx b/webapp/channels/src/components/admin_console/server_logs/logs.test.tsx
new file mode 100644
index 0000000000..ee588467ab
--- /dev/null
+++ b/webapp/channels/src/components/admin_console/server_logs/logs.test.tsx
@@ -0,0 +1,88 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import {LogLevelEnum} from '@mattermost/types/admin';
+
+import {renderWithContext, userEvent, screen, waitFor} from 'tests/react_testing_utils';
+
+import Logs from './logs';
+
+describe('components/admin_console/server_logs/Logs', () => {
+ // Log dataset
+ const logs = [{
+ caller: 'caller 1',
+ job_id: 'job_id 1',
+ level: LogLevelEnum.INFO,
+ msg: 'msg 1',
+ timestamp: 'timestamp 1',
+ worker: 'worker 1',
+ whatever: 'whatever 1',
+ }, {
+ caller: 'caller 2',
+ job_id: 'job_id 2',
+ level: LogLevelEnum.INFO,
+ msg: 'msg 2',
+ timestamp: 'timestamp 2',
+ worker: 'worker 2',
+ whatever: 'whatever 2',
+ }, {
+ caller: 'filtered',
+ job_id: 'filtered',
+ level: LogLevelEnum.INFO,
+ msg: 'filtered message',
+ timestamp: 'filtered',
+ worker: 'filtered',
+ whatever: 'filtered',
+ }];
+ let container: HTMLElement;
+
+ beforeEach(async () => {
+ // Mount server log screen
+ container = renderWithContext(
+ ,
+ ).container;
+
+ // Wait for the logs to be displayed
+ await waitFor(() => {
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
+ expect(screen.queryByText('msg 1')).toBeInTheDocument();
+ expect(screen.queryByText('msg 2')).toBeInTheDocument();
+ expect(screen.queryByText('filtered message')).toBeInTheDocument();
+ });
+ });
+
+ test('should display the logs correctly after loading', () => {
+ expect(container).toMatchSnapshot();
+ });
+
+ test.each(['caller', 'msg', 'worker', 'job_id', 'whatever'])('should search input be performed on %s attribute',
+ async (searchString: string) => {
+ const searchInput = screen.getByTestId('searchInput');
+ userEvent.type(searchInput, searchString);
+
+ await waitFor(() => {
+ expect(screen.queryByText('msg 1')).toBeInTheDocument();
+ expect(screen.queryByText('msg 2')).toBeInTheDocument();
+ expect(screen.queryByText('filtered message')).not.toBeInTheDocument();
+ });
+ });
+
+ test.each(['level', 'timestamp'])('should search input not be performed on %s attribute',
+ async (searchString: string) => {
+ const searchInput = screen.getByTestId('searchInput');
+ userEvent.type(searchInput, searchString);
+
+ await waitFor(() => {
+ expect(screen.queryByText('msg 1')).not.toBeInTheDocument();
+ expect(screen.queryByText('msg 2')).not.toBeInTheDocument();
+ expect(screen.queryByText('filtered message')).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/webapp/channels/src/components/admin_console/server_logs/logs.tsx b/webapp/channels/src/components/admin_console/server_logs/logs.tsx
index 5871f152bd..8512f392e3 100644
--- a/webapp/channels/src/components/admin_console/server_logs/logs.tsx
+++ b/webapp/channels/src/components/admin_console/server_logs/logs.tsx
@@ -20,8 +20,12 @@ import AdminHeader from 'components/widgets/admin_console/admin_header';
import LogList from './log_list';
import PlainLogList from './plain_log_list';
+type LogObjectWithAdditionalInfo = LogObject & {
+ [key: string]: string;
+};
+
type Props = {
- logs: LogObject[];
+ logs: LogObjectWithAdditionalInfo[];
plainLogs: string[];
isPlainLogs: boolean;
actions: {
@@ -121,13 +125,25 @@ export default class Logs extends React.PureComponent {
performSearch = debounce(() => {
const {search} = this.state;
- const filteredLogs = this.props.logs.filter((log) => {
- // to be improved
- return `${log.caller}${log.msg}${log.worker}${log.worker}`.toLowerCase().includes(search.toLowerCase());
- });
+
+ // Excluding level and timestamp from search
+ const excludedKeys = new Set(['level', 'timestamp']);
+
+ const filteredLogs = this.props.logs.filter((log) =>
+ Object.entries(log).some(([key, value]) => {
+ if (excludedKeys.has(key)) {
+ return false;
+ }
+ return String(value).toLowerCase().includes(search.toLowerCase());
+ }),
+ );
this.setState({filteredLogs});
}, 200);
+ componentWillUnmount(): void {
+ this.performSearch.cancel();
+ }
+
onFiltersChange = ({
dateFrom,
dateTo,