Merge pull request #7620 from keithjgrant/6621-inventory-sources-sockets

Inventory sources websockets

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot] 2020-07-20 20:18:51 +00:00 committed by GitHub
commit 676491134d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 183 additions and 3 deletions

View File

@ -19,6 +19,7 @@ import DatalistToolbar from '../../../components/DataListToolbar';
import AlertModal from '../../../components/AlertModal/AlertModal';
import ErrorDetail from '../../../components/ErrorDetail/ErrorDetail';
import InventorySourceListItem from './InventorySourceListItem';
import useWsInventorySources from './useWsInventorySources';
const QS_CONFIG = getQSConfig('inventory', {
not__source: '',
@ -34,7 +35,7 @@ function InventorySourceList({ i18n }) {
const {
isLoading,
error: fetchError,
result: { sources, sourceCount, sourceChoices, sourceChoicesOptions },
result: { result, sourceCount, sourceChoices, sourceChoicesOptions },
request: fetchSources,
} = useRequest(
useCallback(async () => {
@ -44,18 +45,21 @@ function InventorySourceList({ i18n }) {
InventorySourcesAPI.readOptions(),
]);
return {
sources: results[0].data.results,
result: results[0].data.results,
sourceCount: results[0].data.count,
sourceChoices: results[1].data.actions.GET.source.choices,
sourceChoicesOptions: results[1].data.actions,
};
}, [id, search]),
{
sources: [],
result: [],
sourceCount: 0,
sourceChoices: [],
}
);
const sources = useWsInventorySources(result);
const canSyncSources =
sources.length > 0 &&
sources.every(source => source.summary_fields.user_capabilities.start);

View File

@ -55,8 +55,11 @@ const sources = {
describe('<InventorySourceList />', () => {
let wrapper;
let history;
let debug;
beforeEach(async () => {
debug = global.console.debug; // eslint-disable-line prefer-destructuring
global.console.debug = () => {};
InventoriesAPI.readSources.mockResolvedValue(sources);
InventorySourcesAPI.readOptions.mockResolvedValue({
data: {
@ -98,6 +101,7 @@ describe('<InventorySourceList />', () => {
afterEach(() => {
wrapper.unmount();
jest.clearAllMocks();
global.console.debug = debug;
});
test('should mount properly', async () => {

View File

@ -0,0 +1,48 @@
import { useState, useEffect } from 'react';
import useWebsocket from '../../../util/useWebsocket';
export default function useWsJobs(initialSources) {
const [sources, setSources] = useState(initialSources);
const lastMessage = useWebsocket({
jobs: ['status_changed'],
control: ['limit_reached_1'],
});
useEffect(() => {
setSources(initialSources);
}, [initialSources]);
useEffect(
function parseWsMessage() {
if (!lastMessage?.unified_job_id || !lastMessage?.inventory_source_id) {
return;
}
const sourceId = lastMessage.inventory_source_id;
const index = sources.findIndex(s => s.id === sourceId);
if (index > -1) {
setSources(updateSource(sources, index, lastMessage));
}
},
[lastMessage] // eslint-disable-line react-hooks/exhaustive-deps
);
return sources;
}
function updateSource(sources, index, message) {
const source = {
...sources[index],
status: message.status,
last_updated: message.finished,
summary_fields: {
...sources[index].summary_fields,
last_job: {
id: message.unified_job_id,
status: message.status,
finished: message.finished,
},
},
};
return [...sources.slice(0, index), source, ...sources.slice(index + 1)];
}

View File

@ -0,0 +1,124 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import WS from 'jest-websocket-mock';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import useWsInventorySources from './useWsInventorySources';
/*
Jest mock timers dont play well with jest-websocket-mock,
so we'll stub out throttling to resolve immediately
*/
jest.mock('../../../util/useThrottle', () => ({
__esModule: true,
default: jest.fn(val => val),
}));
function TestInner() {
return <div />;
}
function Test({ sources }) {
const syncedSources = useWsInventorySources(sources);
return <TestInner sources={syncedSources} />;
}
describe('useWsInventorySources hook', () => {
let debug;
let wrapper;
beforeEach(() => {
debug = global.console.debug; // eslint-disable-line prefer-destructuring
global.console.debug = () => {};
});
afterEach(() => {
global.console.debug = debug;
});
test('should return sources list', () => {
const sources = [{ id: 1 }];
wrapper = mountWithContexts(<Test sources={sources} />);
expect(wrapper.find('TestInner').prop('sources')).toEqual(sources);
WS.clean();
});
test('should establish websocket connection', async () => {
global.document.cookie = 'csrftoken=abc123';
const mockServer = new WS('wss://localhost/websocket/');
const sources = [{ id: 1 }];
await act(async () => {
wrapper = await mountWithContexts(<Test sources={sources} />);
});
await mockServer.connected;
await expect(mockServer).toReceiveMessage(
JSON.stringify({
xrftoken: 'abc123',
groups: {
jobs: ['status_changed'],
control: ['limit_reached_1'],
},
})
);
WS.clean();
});
test('should update last job status', async () => {
global.document.cookie = 'csrftoken=abc123';
const mockServer = new WS('wss://localhost/websocket/');
const sources = [
{
id: 3,
status: 'running',
summary_fields: {
last_job: {
id: 5,
status: 'running',
},
},
},
];
await act(async () => {
wrapper = await mountWithContexts(<Test sources={sources} />);
});
await mockServer.connected;
await expect(mockServer).toReceiveMessage(
JSON.stringify({
xrftoken: 'abc123',
groups: {
jobs: ['status_changed'],
control: ['limit_reached_1'],
},
})
);
act(() => {
mockServer.send(
JSON.stringify({
unified_job_id: 5,
inventory_source_id: 3,
type: 'job',
status: 'successful',
finished: 'the_time',
})
);
});
wrapper.update();
const source = wrapper.find('TestInner').prop('sources')[0];
expect(source).toEqual({
id: 3,
status: 'successful',
last_updated: 'the_time',
summary_fields: {
last_job: {
id: 5,
status: 'successful',
finished: 'the_time',
},
},
});
WS.clean();
});
});