diff --git a/galasa-ui/messages/de.json b/galasa-ui/messages/de.json index df5543a4..9b443eb3 100644 --- a/galasa-ui/messages/de.json +++ b/galasa-ui/messages/de.json @@ -228,7 +228,10 @@ "filterTrace": "Trace", "downloadButton": "Laufprotokoll herunterladen", "copyPermalinkButton": "Permalink mit ausgewählten Zeilen kopieren", - "selectLinesToCreatePermalink": "Zeilen für Permalink wählen" + "selectLinesToCreatePermalink": "Zeilen für Permalink wählen", + "refreshRunLog": "Aktualisiere das Ausführungsprotokoll", + "scrollToTop": "Zum Anfang des Protokolls scrollen", + "scrollToBottom": "Zum Ende des Protokolls scrollen" }, "MethodsTab": { "title": "Methoden", @@ -389,12 +392,10 @@ "isloading": "Diagramm wird geladen...", "errorLoadingGraph": "Beim Laden des Diagramms ist ein Fehler aufgetreten.", "noTestRunsFound": "Keine Testläufe gefunden.", - "limitExceeded": { "title": "Grenzwert überschritten", "subtitle": "Ihre Abfrage hat mehr als {maxRecords} Ergebnisse zurückgegeben. Es werden die ersten {maxRecords} Datensätze angezeigt. Um dies in Zukunft zu vermeiden, schränken Sie Ihren Zeitrahmen ein oder ändern Sie Ihre Suchkriterien, um weniger Ergebnisse zu erhalten." }, - "timeFrameText": { "range": "Zeige Testläufe von {from} bis {to}" } diff --git a/galasa-ui/messages/en.json b/galasa-ui/messages/en.json index b64c83d4..3033313c 100644 --- a/galasa-ui/messages/en.json +++ b/galasa-ui/messages/en.json @@ -207,7 +207,10 @@ "filterTrace": "Trace", "downloadButton": "Download Run Log", "copyPermalinkButton": "Copy permalink with selected lines", - "selectLinesToCreatePermalink": "Select log lines for permalink" + "selectLinesToCreatePermalink": "Select log lines for permalink", + "refreshRunLog": "Refresh the Run Log", + "scrollToTop": "Scroll to top of log", + "scrollToBottom": "Scroll to bottom of log" }, "MethodsTab": { "title": "Methods", diff --git a/galasa-ui/src/actions/runsAction.ts b/galasa-ui/src/actions/runsAction.ts index d0169d24..ed99a57d 100644 --- a/galasa-ui/src/actions/runsAction.ts +++ b/galasa-ui/src/actions/runsAction.ts @@ -7,6 +7,7 @@ import { ResultArchiveStoreAPIApi, TagsAPIApi } from '@/generated/galasaapi'; import { createAuthenticatedApiConfiguration } from '@/utils/api'; +import { fetchRunDetailLogs } from '@/utils/testRuns'; import { CLIENT_API_VERSION } from '@/utils/constants/common'; export const downloadArtifactFromServer = async (runId: string, artifactUrl: string) => { @@ -92,3 +93,13 @@ export const getExistingTagObjects = async () => { }; } }; + +export const fetchRunLog = async (runId: string) => { + let runLog; + try { + runLog = await fetchRunDetailLogs(runId); + } catch (error: any) { + runLog = 'Error fetching run log: ' + error; + } + return runLog; +}; diff --git a/galasa-ui/src/components/test-runs/test-run-details/LogTab.tsx b/galasa-ui/src/components/test-runs/test-run-details/LogTab.tsx index 0739c212..f3ae62cb 100644 --- a/galasa-ui/src/components/test-runs/test-run-details/LogTab.tsx +++ b/galasa-ui/src/components/test-runs/test-run-details/LogTab.tsx @@ -17,9 +17,13 @@ import { Term, LetterAa, Copy, + Renew, + ArrowUp, + ArrowDown, } from '@carbon/icons-react'; import { handleDownload } from '@/utils/artifacts'; import { useTranslations } from 'next-intl'; +import { fetchRunLog } from '@/actions/runsAction'; interface LogLine { content: string; @@ -43,6 +47,7 @@ enum RegexFlags { interface LogTabProps { logs: string; initialLine?: number; + runId: string; } interface selectedRange { @@ -55,10 +60,11 @@ interface selectedRange { const SELECTION_CHANGE_EVENT = 'selectionchange'; const HASH_CHANGE_EVENT = 'hashchange'; -export default function LogTab({ logs, initialLine }: LogTabProps) { +export default function LogTab({ logs, initialLine, runId }: LogTabProps) { const translations = useTranslations('LogTab'); const [logContent, setLogContent] = useState(''); + const [isRefreshing, setIsRefreshing] = useState(false); const [processedLines, setProcessedLines] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(''); @@ -84,6 +90,7 @@ export default function LogTab({ logs, initialLine }: LogTabProps) { ); const logContainerRef = useRef(null); + const scrollContainerRef = useRef(null); const debounceTimeoutRef = useRef(null); const DEBOUNCE_DELAY_MILLISECONDS = 300; @@ -162,6 +169,48 @@ export default function LogTab({ logs, initialLine }: LogTabProps) { } }; + const handleRefreshLog = async () => { + setIsRefreshing(true); + + try { + // Fetch fresh log from the server + const newRunLog = await fetchRunLog(runId); + + setLogContent(newRunLog); + + // Reset search and filters + setSearchTerm(''); + setDebouncedSearchTerm(''); + setCurrentMatchIndex(-1); + setTotalMatches(0); + setSearchCache(new Map()); + } catch (error) { + console.error('Error refreshing logs:', error); + // Fallback to existing logs if fetch fails + setLogContent(logs); + } finally { + setIsRefreshing(false); + } + }; + + const scrollToTop = () => { + if (scrollContainerRef.current) { + scrollContainerRef.current.scrollTo({ + top: 0, + behavior: 'smooth', + }); + } + }; + + const scrollToBottom = () => { + if (scrollContainerRef.current) { + scrollContainerRef.current.scrollTo({ + top: scrollContainerRef.current.scrollHeight, + behavior: 'smooth', + }); + } + }; + // Memoized regex creation to avoid recreating the same regex repeatedly const searchRegex = useMemo(() => { let regex: RegExp | null = null; @@ -685,8 +734,30 @@ export default function LogTab({ logs, initialLine }: LogTabProps) { className={!selectedRange?.startLine ? styles.buttonDisabled : ''} data-testid="icon-button-copy-permalink" /> +