From 981c9527b279e3854224c10f53675071a99f1f55 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Fri, 10 Jul 2020 15:55:12 -0700 Subject: [PATCH] add template list websocket support --- .../Template/TemplateList/TemplateList.jsx | 18 +-- .../Template/TemplateList/useWsTemplates.js | 112 ++++++++++++++++++ 2 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 awx/ui_next/src/screens/Template/TemplateList/useWsTemplates.js diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx index 5be5cf580f..6e4a831afa 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx @@ -17,7 +17,7 @@ import PaginatedDataList, { } from '../../../components/PaginatedDataList'; import useRequest, { useDeleteItems } from '../../../util/useRequest'; import { getQSConfig, parseQueryString } from '../../../util/qs'; - +import useWsTemplates from './useWsTemplates'; import AddDropDownButton from '../../../components/AddDropDownButton'; import TemplateListItem from './TemplateListItem'; @@ -36,27 +36,27 @@ function TemplateList({ i18n }) { const [selected, setSelected] = useState([]); const { - result: { templates, count, jtActions, wfjtActions }, + result: { results, count, jtActions, wfjtActions }, error: contentError, isLoading, request: fetchTemplates, } = useRequest( useCallback(async () => { const params = parseQueryString(QS_CONFIG, location.search); - const results = await Promise.all([ + const responses = await Promise.all([ UnifiedJobTemplatesAPI.read(params), JobTemplatesAPI.readOptions(), WorkflowJobTemplatesAPI.readOptions(), ]); return { - templates: results[0].data.results, - count: results[0].data.count, - jtActions: results[1].data.actions, - wfjtActions: results[2].data.actions, + results: responses[0].data.results, + count: responses[0].data.count, + jtActions: responses[1].data.actions, + wfjtActions: responses[2].data.actions, }; }, [location]), { - templates: [], + results: [], count: 0, jtActions: {}, wfjtActions: {}, @@ -67,6 +67,8 @@ function TemplateList({ i18n }) { fetchTemplates(); }, [fetchTemplates]); + const templates = useWsTemplates(results); + const isAllSelected = selected.length === templates.length && selected.length > 0; const { diff --git a/awx/ui_next/src/screens/Template/TemplateList/useWsTemplates.js b/awx/ui_next/src/screens/Template/TemplateList/useWsTemplates.js new file mode 100644 index 0000000000..052d2a9197 --- /dev/null +++ b/awx/ui_next/src/screens/Template/TemplateList/useWsTemplates.js @@ -0,0 +1,112 @@ +import { useState, useEffect, useRef } from 'react'; + +export default function useWsTemplates(initialTemplates) { + const [templates, setTemplates] = useState(initialTemplates); + + const [lastMessage, setLastMessage] = useState(null); + const ws = useRef(null); + + useEffect(() => { + setTemplates(initialTemplates); + }, [initialTemplates]); + + // x = { + // unified_job_id: 548, + // status: 'pending', + // type: 'job', + // group_name: 'jobs', + // unified_job_template_id: 26, + // }; + useEffect( + function parseWsMessage() { + if (!lastMessage?.unified_job_id) { + return; + } + const index = templates.findIndex( + t => t.id === lastMessage.unified_job_template_id + ); + if (index === -1) { + return; + } + + const template = templates[index]; + const updated = [...templates]; + updated[index] = updateTemplate(template, lastMessage); + setTemplates(updated); + }, + [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 templates; +} + +function updateTemplate(template, message) { + const recentJobs = [...(template.summary_fields.recent_jobs || [])]; + const job = { + id: message.unified_job_id, + status: message.status, + finished: message.finished, + type: message.type, + }; + const index = recentJobs.findIndex(j => j.id === job.id); + if (index > -1) { + recentJobs[index] = { + ...recentJobs[index], + ...job, + }; + } else { + recentJobs.unshift(job); + } + + return { + ...template, + summary_fields: { + ...template.summary_fields, + recent_jobs: recentJobs.slice(0, 10), + }, + }; +}