diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 65e33fd915..e76e6e8b13 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -30,6 +30,7 @@ "rc-tooltip": "^5.2.2", "react": "^18.3.1", "react-avatar": "^5.0.3", + "react-bus": "^4.0.1", "react-dom": "^18.3.1", "react-helmet": "^6.1.0", "react-hook-form": "^7.53.0", @@ -13904,6 +13905,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/mnth": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mnth/-/mnth-2.0.0.tgz", @@ -21492,6 +21499,19 @@ "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-bus": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-bus/-/react-bus-4.0.1.tgz", + "integrity": "sha512-tzPWE23WN0U9v3YaGKAlLW7GXYv1YkCUgWcnzm8HDtfBeD2vDvK8PYHkJVrwMdzg5BleHcxejtdKYfIYZxD7PQ==", + "license": "MIT", + "dependencies": { + "@types/react": "^18.0.8", + "mitt": "^3.0.1" + }, + "peerDependencies": { + "react": ">=17.0.0 || ^19.0.0-0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index ae701fc95f..f4dfc7c09e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -119,6 +119,7 @@ "rc-tooltip": "^5.2.2", "react": "^18.3.1", "react-avatar": "^5.0.3", + "react-bus": "^4.0.1", "react-dom": "^18.3.1", "react-helmet": "^6.1.0", "react-hook-form": "^7.53.0", diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 8820bd30d2..c02da1084c 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Provider as BusProvider } from 'react-bus'; import { createRoot } from 'react-dom/client'; import { Provider } from 'react-redux'; import { RouterProvider } from 'react-router-dom'; @@ -36,7 +37,9 @@ if (container) { root.render( - + + + , ); diff --git a/frontend/src/pages/Runs/Details/Events/List/index.tsx b/frontend/src/pages/Runs/Details/Events/List/index.tsx index 79ccb54436..fb6610033e 100644 --- a/frontend/src/pages/Runs/Details/Events/List/index.tsx +++ b/frontend/src/pages/Runs/Details/Events/List/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useListener } from 'react-bus'; import { useTranslation } from 'react-i18next'; import { useNavigate, useParams } from 'react-router-dom'; import Button from '@cloudscape-design/components/button'; @@ -12,13 +13,15 @@ import { useLazyGetAllEventsQuery } from 'services/events'; import { useColumnsDefinitions } from 'pages/Events/List/hooks/useColumnDefinitions'; +import { RUN_DETAILS_REFRESH_LIST_EVENT } from '../../constants'; + export const EventsList = () => { const { t } = useTranslation(); const params = useParams(); const paramRunId = params.runId ?? ''; const navigate = useNavigate(); - const { data, isLoading, isLoadingMore } = useInfiniteScroll({ + const { data, isLoading, isLoadingMore, refreshList } = useInfiniteScroll({ useLazyQuery: useLazyGetAllEventsQuery, args: { limit: DEFAULT_TABLE_PAGE_SIZE, within_runs: [paramRunId] }, @@ -28,6 +31,8 @@ export const EventsList = () => { }), }); + useListener(RUN_DETAILS_REFRESH_LIST_EVENT, refreshList); + const { items, collectionProps } = useCollection(data, { selection: {}, }); diff --git a/frontend/src/pages/Runs/Details/Inspect/index.tsx b/frontend/src/pages/Runs/Details/Inspect/index.tsx index 5dc9e9a46b..68c4b7bcbb 100644 --- a/frontend/src/pages/Runs/Details/Inspect/index.tsx +++ b/frontend/src/pages/Runs/Details/Inspect/index.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useMemo } from 'react'; +import { useListener } from 'react-bus'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; @@ -6,6 +7,8 @@ import { CodeEditor, Container, Header, Loader } from 'components'; import { useGetRunQuery } from 'services/run'; +import { RUN_DETAILS_REFRESH_LIST_EVENT } from '../constants'; + interface AceEditorElement extends HTMLElement { env?: { editor?: { @@ -20,11 +23,17 @@ export const RunInspect = () => { const paramProjectName = params.projectName ?? ''; const paramRunId = params.runId ?? ''; - const { data: runData, isLoading } = useGetRunQuery({ + const { + data: runData, + isLoading, + refetch, + } = useGetRunQuery({ project_name: paramProjectName, id: paramRunId, }); + useListener(RUN_DETAILS_REFRESH_LIST_EVENT, refetch); + const jsonContent = useMemo(() => { if (!runData) return ''; return JSON.stringify(runData, null, 2); diff --git a/frontend/src/pages/Runs/Details/Jobs/Metrics/index.tsx b/frontend/src/pages/Runs/Details/Jobs/Metrics/index.tsx index dd088c418b..db89033984 100644 --- a/frontend/src/pages/Runs/Details/Jobs/Metrics/index.tsx +++ b/frontend/src/pages/Runs/Details/Jobs/Metrics/index.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useMemo } from 'react'; +import { useListener } from 'react-bus'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; @@ -7,6 +8,7 @@ import { Box, ColumnLayout, Container, Header, LineChart } from 'components'; import { riseRouterException } from 'libs'; import { useGetRunQuery } from 'services/run'; +import { RUN_DETAILS_REFRESH_LIST_EVENT } from '../../constants'; import { bytesFormatter, formatPercent, formatTime } from './helpers'; import { useMetricsData } from './useMetricsData'; @@ -32,7 +34,14 @@ export const JobMetrics: React.FC = () => { return runData.jobs.find((job) => job.job_spec.job_name === paramJobName) ?? null; }, [runData]); - const { cpuChartProps, memoryChartProps, eachGPUChartProps, eachGPUMemoryChartProps, isLoading } = useMetricsData({ + const { + cpuChartProps, + memoryChartProps, + eachGPUChartProps, + eachGPUMemoryChartProps, + isLoading, + refetch: refetchMetrics, + } = useMetricsData({ project_name: paramProjectName, run_name: runData?.run_spec.run_name ?? '', run_id: runData?.id ?? '', @@ -40,6 +49,8 @@ export const JobMetrics: React.FC = () => { limit: 1000, }); + useListener(RUN_DETAILS_REFRESH_LIST_EVENT, refetchMetrics); + const statusType = isLoading || isLoadingRun ? 'loading' : 'finished'; useEffect(() => { diff --git a/frontend/src/pages/Runs/Details/Jobs/Metrics/useMetricsData.ts b/frontend/src/pages/Runs/Details/Jobs/Metrics/useMetricsData.ts index 758e3faa33..70bc1d6e5a 100644 --- a/frontend/src/pages/Runs/Details/Jobs/Metrics/useMetricsData.ts +++ b/frontend/src/pages/Runs/Details/Jobs/Metrics/useMetricsData.ts @@ -15,7 +15,11 @@ import { import { bytesFormatter, getChartProps } from './helpers'; export const useMetricsData = (params: TJobMetricsRequestParams) => { - const { data: metricsData, isLoading } = useGetMetricsQuery(params, { + const { + data: metricsData, + isLoading, + refetch, + } = useGetMetricsQuery(params, { skip: !params.run_name, }); @@ -76,5 +80,5 @@ export const useMetricsData = (params: TJobMetricsRequestParams) => { }); }, [metricsData]); - return { cpuChartProps, eachGPUChartProps, memoryChartProps, eachGPUMemoryChartProps, isLoading }; + return { cpuChartProps, eachGPUChartProps, memoryChartProps, eachGPUMemoryChartProps, isLoading, refetch }; }; diff --git a/frontend/src/pages/Runs/Details/Logs/index.tsx b/frontend/src/pages/Runs/Details/Logs/index.tsx index 2c836bb08d..4d12520bba 100644 --- a/frontend/src/pages/Runs/Details/Logs/index.tsx +++ b/frontend/src/pages/Runs/Details/Logs/index.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { useListener } from 'react-bus'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import classNames from 'classnames'; @@ -10,6 +11,7 @@ import { useLazyGetProjectLogsQuery } from 'services/project'; import { useGetRunQuery } from 'services/run'; import { LogRow } from './components/LogRow'; +import { RUN_DETAILS_REFRESH_LIST_EVENT } from '../constants'; import { decodeLogs, getJobSubmissionId } from './helpers'; import { IProps } from './types'; @@ -116,6 +118,10 @@ export const Logs: React.FC = ({ className, projectName, runName, jobSub getLogItems(); }, []); + const refreshLogs = useCallback(() => getLogItems(), []); + + useListener(RUN_DETAILS_REFRESH_LIST_EVENT, refreshLogs); + useLayoutEffect(() => { if (logsForView.length && logsForView.length <= LIMIT_LOG_ROWS) { scrollToBottom(); diff --git a/frontend/src/pages/Runs/Details/RunDetails/index.tsx b/frontend/src/pages/Runs/Details/RunDetails/index.tsx index 408d4cb16b..9a34b6af74 100644 --- a/frontend/src/pages/Runs/Details/RunDetails/index.tsx +++ b/frontend/src/pages/Runs/Details/RunDetails/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useListener } from 'react-bus'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import { format } from 'date-fns'; @@ -28,9 +29,9 @@ import { getRunListItemRegion, getRunListItemResources, getRunListItemSchedule, - getRunListItemServiceUrl, getRunListItemSpotLabelKey, } from '../../List/helpers'; +import { RUN_DETAILS_REFRESH_LIST_EVENT } from '../constants'; import { EventsList } from '../Events/List'; import { JobList } from '../Jobs/List'; import { ConnectToRunWithDevEnvConfiguration } from './ConnectToRunWithDevEnvConfiguration'; @@ -42,11 +43,17 @@ export const RunDetails = () => { const paramProjectName = params.projectName ?? ''; const paramRunId = params.runId ?? ''; - const { data: runData, isLoading: isLoadingRun } = useGetRunQuery({ + const { + data: runData, + isLoading: isLoadingRun, + refetch, + } = useGetRunQuery({ project_name: paramProjectName, id: paramRunId, }); + useListener(RUN_DETAILS_REFRESH_LIST_EVENT, refetch); + const schedule = runData ? getRunListItemSchedule(runData) : null; const nextTriggeredAt = runData ? runData.next_triggered_at : null; diff --git a/frontend/src/pages/Runs/Details/constants.ts b/frontend/src/pages/Runs/Details/constants.ts index 7a63d3f95c..be8b1da878 100644 --- a/frontend/src/pages/Runs/Details/constants.ts +++ b/frontend/src/pages/Runs/Details/constants.ts @@ -5,3 +5,5 @@ export enum CodeTab { Events = 'events', Inspect = 'inspect', } + +export const RUN_DETAILS_REFRESH_LIST_EVENT = 'RUN_DETAILS_REFRESH_LIST_EVENT'; diff --git a/frontend/src/pages/Runs/Details/index.tsx b/frontend/src/pages/Runs/Details/index.tsx index 5195b4fdc0..9a1912a0bf 100644 --- a/frontend/src/pages/Runs/Details/index.tsx +++ b/frontend/src/pages/Runs/Details/index.tsx @@ -1,4 +1,5 @@ import React, { useEffect } from 'react'; +import { useBus } from 'react-bus'; import { useTranslation } from 'react-i18next'; import { Outlet, /*useNavigate,*/ useParams } from 'react-router-dom'; import Button from '@cloudscape-design/components/button'; @@ -15,7 +16,7 @@ import { isAvailableStoppingForRun, // isAvailableDeletingForRun, } from '../utils'; -import { CodeTab } from './constants'; +import { CodeTab, RUN_DETAILS_REFRESH_LIST_EVENT } from './constants'; import styles from './styles.module.scss'; @@ -26,12 +27,12 @@ export const RunDetailsPage: React.FC = () => { const paramProjectName = params.projectName ?? ''; const paramRunId = params.runId ?? ''; const [pushNotification] = useNotifications(); + const bus = useBus(); const { data: runData, error: runError, isLoading, - refetch, } = useGetRunQuery( { project_name: paramProjectName, @@ -108,6 +109,10 @@ export const RunDetailsPage: React.FC = () => { }); }; + const refreshHandle = () => { + bus.emit(RUN_DETAILS_REFRESH_LIST_EVENT); + }; + // const deleteClickHandle = () => { // if (!runData) { // return; @@ -157,7 +162,7 @@ export const RunDetailsPage: React.FC = () => { iconName="refresh" disabled={isLoading} ariaLabel={t('common.refresh')} - onClick={refetch} + onClick={refreshHandle} /> }