From 96571179410bef5fa51c7d3dba7f58893b6835d2 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Thu, 2 Jul 2020 09:29:56 -0700 Subject: [PATCH] add ProjectList websocket support --- .../Project/ProjectList/ProjectList.jsx | 9 +- .../Project/ProjectList/useWsProjects.js | 85 +++++++++++++++++++ 2 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 awx/ui_next/src/screens/Project/ProjectList/useWsProjects.js diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx index e82a31c4ae..a39f246205 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx @@ -13,6 +13,7 @@ import PaginatedDataList, { ToolbarAddButton, ToolbarDeleteButton, } from '../../../components/PaginatedDataList'; +import useWsProjects from './useWsProjects'; import { getQSConfig, parseQueryString } from '../../../util/qs'; import ProjectListItem from './ProjectListItem'; @@ -29,7 +30,7 @@ function ProjectList({ i18n }) { const [selected, setSelected] = useState([]); const { - result: { projects, itemCount, actions }, + result: { results, itemCount, actions }, error: contentError, isLoading, request: fetchProjects, @@ -41,13 +42,13 @@ function ProjectList({ i18n }) { ProjectsAPI.readOptions(), ]); return { - projects: response.data.results, + results: response.data.results, itemCount: response.data.count, actions: actionsResponse.data.actions, }; }, [location]), { - projects: [], + results: [], itemCount: 0, actions: {}, } @@ -57,6 +58,8 @@ function ProjectList({ i18n }) { fetchProjects(); }, [fetchProjects]); + const projects = useWsProjects(results); + const isAllSelected = selected.length === projects.length && selected.length > 0; const { diff --git a/awx/ui_next/src/screens/Project/ProjectList/useWsProjects.js b/awx/ui_next/src/screens/Project/ProjectList/useWsProjects.js new file mode 100644 index 0000000000..a5c46319ba --- /dev/null +++ b/awx/ui_next/src/screens/Project/ProjectList/useWsProjects.js @@ -0,0 +1,85 @@ +import { useState, useEffect, useRef } from 'react'; + +export default function useWsProjects(initialProjects) { + const [projects, setProjects] = useState(initialProjects); + const [lastMessage, setLastMessage] = useState(null); + const ws = useRef(null); + + useEffect(() => { + setProjects(initialProjects); + }, [initialProjects]); + + useEffect(() => { + if (!lastMessage?.unified_job_id || lastMessage.type !== 'project_update') { + return; + } + const index = projects.findIndex(p => p.id === lastMessage.project_id); + if (index === -1) { + return; + } + + const project = projects[index]; + const updatedProject = { + ...project, + summary_fields: { + ...project.summary_fields, + last_job: { + id: lastMessage.unified_job_id, + status: lastMessage.status, + finished: lastMessage.finished, + }, + }, + }; + setProjects([ + ...projects.slice(0, index), + updatedProject, + ...projects.slice(index + 1), + ]); + }, [lastMessage]); // eslint-disable-line react-hooks/exhaustive-deps + + useEffect(() => { + ws.current = new WebSocket(`wss://${window.location.host}/websocket/`); + + const connect = () => { + const xrftoken = `; ${document.cookie}` + .split('; csrftoken=') + .pop() + .split(';') + .shift(); + ws.current.send( + JSON.stringify({ + xrftoken, + groups: { + jobs: ['status_changed'], + control: ['limit_reached_1'], + }, + }) + ); + }; + ws.current.onopen = connect; + + ws.current.onmessage = e => { + setLastMessage(JSON.parse(e.data)); + }; + + ws.current.onclose = e => { + // eslint-disable-next-line no-console + console.debug('Socket closed. Reconnecting...', e); + setTimeout(() => { + connect(); + }, 1000); + }; + + ws.current.onerror = err => { + // eslint-disable-next-line no-console + console.debug('Socket error: ', err, 'Disconnecting...'); + ws.current.close(); + }; + + return () => { + ws.current.close(); + }; + }, []); + + return projects; +}