From 8fba194030e219fea26ddf239dbe3659d8eb4fce Mon Sep 17 00:00:00 2001 From: Brendan Gibson Date: Fri, 21 Jun 2024 13:31:24 -0600 Subject: [PATCH 1/3] Always show log bar --- src/components/LogList/LogActionBar.tsx | 105 ++++++++++++------------ src/components/LogList/index.tsx | 11 +-- src/hooks/useLogData/index.ts | 4 +- src/pages/Task/index.tsx | 9 ++ 4 files changed, 65 insertions(+), 64 deletions(-) diff --git a/src/components/LogList/LogActionBar.tsx b/src/components/LogList/LogActionBar.tsx index f48efc8a..17e038f4 100644 --- a/src/components/LogList/LogActionBar.tsx +++ b/src/components/LogList/LogActionBar.tsx @@ -33,34 +33,35 @@ const LogActionBar: React.FC = ({ }) => { const { addNotification } = useNotifications(); const { t } = useTranslation(); + + console.log('LogActionBar render'); return ( - {data && data.length > 0 && ( - <> - - { - search.search(e); - }} - onSubmit={() => { - search.nextResult(); - }} - noClear - customIcon={['search', 'sm']} - customIconElement={ - search.result.active && - search.result.result.length > 0 && ( - - {search.result.current + 1}/{search.result.result.length} - - ) - } - infoMsg={t('task.log-search-tip') ?? ''} - /> - - - + <> + + { + search.search(e); + }} + onSubmit={() => { + search.nextResult(); + }} + noClear + customIcon={['search', 'sm']} + customIconElement={ + search.result.active && + search.result.result.length > 0 && ( + + {search.result.current + 1}/{search.result.result.length} + + ) + } + infoMsg={t('task.log-search-tip') ?? ''} + /> + + + {data && data.length > 0 && ( + )} - - - + + + - {setFullscreen && ( - - )} - - - )} + {setFullscreen && ( + + )} + + ); }; diff --git a/src/components/LogList/index.tsx b/src/components/LogList/index.tsx index cb74c6b9..b735088f 100644 --- a/src/components/LogList/index.tsx +++ b/src/components/LogList/index.tsx @@ -6,7 +6,6 @@ import { LogData, LogItem, SearchState } from '../../hooks/useLogData'; import { useDebounce } from 'use-debounce'; import { AsyncStatus, Log } from '../../types'; import { lighten } from 'polished'; -import LogActionBar from './LogActionBar'; import { getTimestampString } from '../../utils/date'; import { TimezoneContext } from '../TimezoneProvider'; import { MeasuredCellParent } from 'react-virtualized/dist/es/CellMeasurer'; @@ -29,7 +28,7 @@ type LogProps = { const LIST_MAX_HEIGHT = 400; -const LogList: React.FC = ({ logdata, fixedHeight, onScroll, downloadUrl, setFullscreen }) => { +const LogList: React.FC = ({ logdata, fixedHeight, onScroll }) => { const { timezone } = useContext(TimezoneContext); const { t } = useTranslation(); const rows = logdata.logs; @@ -112,14 +111,6 @@ const LogList: React.FC = ({ logdata, fixedHeight, onScroll, downloadU return (
- - {rows.length === 0 && ['Ok', 'Error'].includes(logdata.preloadStatus) && logdata.status === 'NotAsked' && (
{t('task.no-preload-logs')}
)} diff --git a/src/hooks/useLogData/index.ts b/src/hooks/useLogData/index.ts index d38c8985..749be37d 100644 --- a/src/hooks/useLogData/index.ts +++ b/src/hooks/useLogData/index.ts @@ -147,7 +147,7 @@ const useLogData = ({ preload, paused, url, pagesize }: LogDataSettings): LogDat }, [paused, url, status, fetchLogs]); useEffect(() => { - // For preload to happen following rules has to be matched + // For preload to happen following rules have to be matched // paused -> Run has to be on running state // status -> This should always be NotAsked if paused is on. Check just in case // preload -> Run has to be runnign @@ -172,7 +172,7 @@ const useLogData = ({ preload, paused, url, pagesize }: LogDataSettings): LogDat // Post finish polling // In some cases all logs might not be there after task finishes. For this, lets poll new logs every 10sec until - // there is no new lines + // there are no new lines useEffect(() => { let t: number; if (status === 'Ok' && postPoll) { diff --git a/src/pages/Task/index.tsx b/src/pages/Task/index.tsx index 392fc668..6c319e03 100755 --- a/src/pages/Task/index.tsx +++ b/src/pages/Task/index.tsx @@ -39,6 +39,7 @@ import useTaskCards, { taskCardPath } from '../../components/MFCard/useTaskCards import DynamicCardIframe from '../../components/MFCard/DynamicCardIframe'; import Button from '../../components/Button'; import Icon from '../../components/Icon'; +import LogActionBar from '../../components/LogList/LogActionBar'; // // Typedef @@ -357,6 +358,14 @@ const Task: React.FC = ({ label: t('task.std-out'), component: ( <> + + Date: Wed, 24 Jul 2024 08:35:00 -0600 Subject: [PATCH 2/3] Show log bar --- src/components/LogList/LogActionBar.tsx | 42 +++++++++++++-------- src/components/LogList/index.tsx | 16 ++++---- src/hooks/useLogData/index.ts | 49 ++++++++++++++----------- src/pages/Task/index.tsx | 9 +++++ 4 files changed, 72 insertions(+), 44 deletions(-) diff --git a/src/components/LogList/LogActionBar.tsx b/src/components/LogList/LogActionBar.tsx index 17e038f4..188b43ab 100644 --- a/src/components/LogList/LogActionBar.tsx +++ b/src/components/LogList/LogActionBar.tsx @@ -2,11 +2,12 @@ import React from 'react'; import styled from 'styled-components'; import { useTranslation } from 'react-i18next'; import Button from '../Button'; -import { NotificationType, useNotifications } from '../Notifications'; +import { NotificationType, useNotifications, Notification } from '../Notifications'; import copy from 'copy-to-clipboard'; import Icon from '../Icon'; import { LocalSearchType, LogItem } from '../../hooks/useLogData'; import FilterInput from '../FilterInput'; +import { TFunction } from 'i18next'; // // Typedef @@ -20,6 +21,28 @@ type LogActionBarProps = { spaceAround?: boolean; }; +const handleFilterChange = (search: LocalSearchType) => (key: string) => { + search.search(key); +}; + +const handleFilterSubmit = (search: LocalSearchType) => () => { + search.nextResult(); +}; + +const handleCopyButtonClick = + ( + addNotification: (...notification: Notification[]) => void, + data: LogItem[], + t: TFunction<'translation', undefined, 'translation'>, + ) => + () => { + copy(data.map((item) => (typeof item === 'object' ? item.line : item)).join('\n')); + addNotification({ + type: NotificationType.Info, + message: t('task.all-logs-copied'), + }); + }; + // // Component // @@ -34,19 +57,14 @@ const LogActionBar: React.FC = ({ const { addNotification } = useNotifications(); const { t } = useTranslation(); - console.log('LogActionBar render'); return ( <> { - search.search(e); - }} - onSubmit={() => { - search.nextResult(); - }} + onChange={handleFilterChange(search)} + onSubmit={handleFilterSubmit(search)} noClear customIcon={['search', 'sm']} customIconElement={ @@ -66,13 +84,7 @@ const LogActionBar: React.FC = ({ data-testid="log-action-button" title={t('task.copy-logs-to-clipboard') ?? ''} iconOnly - onClick={() => { - copy(data.map((item) => (typeof item === 'object' ? item.line : item)).join('\n')); - addNotification({ - type: NotificationType.Info, - message: t('task.all-logs-copied'), - }); - }} + onClick={handleCopyButtonClick(addNotification, data, t)} > diff --git a/src/components/LogList/index.tsx b/src/components/LogList/index.tsx index b735088f..6e078448 100644 --- a/src/components/LogList/index.tsx +++ b/src/components/LogList/index.tsx @@ -109,6 +109,14 @@ const LogList: React.FC = ({ logdata, fixedHeight, onScroll }) => { [onScroll], ); + const handleScroll = (args: { scrollTop: number; clientHeight: number; scrollHeight: number }) => { + if (args.scrollTop + args.clientHeight >= args.scrollHeight) { + setStickBottom(true); + } else if (stickBottom) { + setStickBottom(false); + } + }; + return (
{rows.length === 0 && ['Ok', 'Error'].includes(logdata.preloadStatus) && logdata.status === 'NotAsked' && ( @@ -128,13 +136,7 @@ const LogList: React.FC = ({ logdata, fixedHeight, onScroll }) => { rowHeight={cache.rowHeight} onRowsRendered={onRowsRendered} deferredMeasurementCache={cache} - onScroll={(args: { scrollTop: number; clientHeight: number; scrollHeight: number }) => { - if (args.scrollTop + args.clientHeight >= args.scrollHeight) { - setStickBottom(true); - } else if (stickBottom) { - setStickBottom(false); - } - }} + onScroll={handleScroll} rowRenderer={({ index, style, diff --git a/src/hooks/useLogData/index.ts b/src/hooks/useLogData/index.ts index 749be37d..4d0becea 100644 --- a/src/hooks/useLogData/index.ts +++ b/src/hooks/useLogData/index.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { Log, AsyncStatus, APIError } from '../../types'; import { DataModel, defaultError } from '../../hooks/useResource'; import { apiHttp } from '../../constants'; @@ -78,7 +78,7 @@ const useLogData = ({ preload, paused, url, pagesize }: LogDataSettings): LogDat .then((response) => response.json()) .then((result: DataModel | APIError) => { if (isOkResult(result)) { - // Check if there was any new lines. If there wasnt, lets cancel post finish polling. + // Check if there was any new lines. If there wasn't, let's cancel post finish polling. // Or if was postpoll and we didnt get any results if ( (result.data.length > 0 && logs.length > 0 && result.data[0].row === logs.length - 1) || @@ -213,31 +213,34 @@ const useLogData = ({ preload, paused, url, pagesize }: LogDataSettings): LogDat const [searchResult, setSearchResult] = useState(emptySearchResult); - function search(str: string) { - if (!str) { - return setSearchResult(emptySearchResult); - } - const query = str.toLowerCase(); - const results = logs - .filter(filterbySearchTerm) - .filter((line) => line.line.toLowerCase().indexOf(query) > -1) - .map((item) => { - const index = item.line.toLowerCase().indexOf(query); - return { - line: item.row, - char: [index, index + str.length] as [number, number], - }; - }); - setSearchResult({ active: true, result: results, current: 0, query: str }); - } + const search = useCallback( + (str: string) => { + if (!str) { + return setSearchResult(emptySearchResult); + } + const query = str.toLowerCase(); + const results = logs + .filter(filterbySearchTerm) + .filter((line) => line.line.toLowerCase().indexOf(query) > -1) + .map((item) => { + const index = item.line.toLowerCase().indexOf(query); + return { + line: item.row, + char: [index, index + str.length] as [number, number], + }; + }); + setSearchResult({ active: true, result: results, current: 0, query: str }); + }, + [logs], + ); - function nextResult() { + const nextResult = useCallback(() => { if (searchResult.current === searchResult.result.length - 1) { setSearchResult((cur) => ({ ...cur, current: 0 })); } else { setSearchResult((cur) => ({ ...cur, current: cur.current + 1 })); } - } + }, [searchResult]); // Clean up on url change useEffect(() => { @@ -251,7 +254,9 @@ const useLogData = ({ preload, paused, url, pagesize }: LogDataSettings): LogDat }; }, [url]); - return { logs, status, preloadStatus, error, loadMore, localSearch: { search, nextResult, result: searchResult } }; + const localSearch = useMemo(() => ({ search, nextResult, result: searchResult }), [nextResult, search, searchResult]); + + return { logs, status, preloadStatus, error, loadMore, localSearch }; }; function filterbySearchTerm(item: LogItem): item is Log { diff --git a/src/pages/Task/index.tsx b/src/pages/Task/index.tsx index 6c319e03..ca3443ef 100755 --- a/src/pages/Task/index.tsx +++ b/src/pages/Task/index.tsx @@ -192,6 +192,7 @@ const Task: React.FC = ({ paused: !isCurrentTaskFinished, preload: task?.status === 'running', url: `${logUrl}out?attempt_id=${attemptId.toString()}`, + pagesize: 50, }); // Error logs @@ -199,6 +200,7 @@ const Task: React.FC = ({ paused: !isCurrentTaskFinished, preload: task?.status === 'running', url: `${logUrl}err?attempt_id=${attemptId.toString()}`, + pagesize: 50, }); const onUpdate = useCallback((data: Artifact[]) => { @@ -392,6 +394,13 @@ const Task: React.FC = ({ label: t('task.std-err'), component: ( <> + Date: Wed, 24 Jul 2024 08:39:00 -0600 Subject: [PATCH 3/3] Update log tests --- .../LogList/__tests__/LogActionBar.test.cypress.tsx | 4 ++-- src/components/LogList/__tests__/LogList.test.cypress.tsx | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/components/LogList/__tests__/LogActionBar.test.cypress.tsx b/src/components/LogList/__tests__/LogActionBar.test.cypress.tsx index 51181787..51db2c1d 100644 --- a/src/components/LogList/__tests__/LogActionBar.test.cypress.tsx +++ b/src/components/LogList/__tests__/LogActionBar.test.cypress.tsx @@ -15,14 +15,14 @@ const Search = { }; describe('LogActionBar', () => { - it('Should render empty action bar since there is no log data', () => { + it('Should render only download and fullscreen buttons since there is no log data', () => { mount( , ); - gid('log-action-bar').children().should('have.length', 0); + gid('log-action-bar').children().should('have.length', 2); }); it('Should render action bar with two buttons', () => { diff --git a/src/components/LogList/__tests__/LogList.test.cypress.tsx b/src/components/LogList/__tests__/LogList.test.cypress.tsx index 1884633b..80e1dcd0 100644 --- a/src/components/LogList/__tests__/LogList.test.cypress.tsx +++ b/src/components/LogList/__tests__/LogList.test.cypress.tsx @@ -32,15 +32,7 @@ function generateLines(amount: number) { const LIST_CONTAINER_CLASS = 'ReactVirtualized__List'; describe('LogActionBar', () => { - it('Should render empty wrapper in initial situation', () => { - mount( - - - , - ); - gid('loglist-wrapper').children().should('have.length', 1); - }); it('Should render message about empty preload when preload was empty or error and final fetch is not started', () => { mount(