mirror of
https://github.com/ansible/awx.git
synced 2026-05-09 02:17:37 -02:30
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:
@@ -19,6 +19,7 @@ import DatalistToolbar from '../../../components/DataListToolbar';
|
|||||||
import AlertModal from '../../../components/AlertModal/AlertModal';
|
import AlertModal from '../../../components/AlertModal/AlertModal';
|
||||||
import ErrorDetail from '../../../components/ErrorDetail/ErrorDetail';
|
import ErrorDetail from '../../../components/ErrorDetail/ErrorDetail';
|
||||||
import InventorySourceListItem from './InventorySourceListItem';
|
import InventorySourceListItem from './InventorySourceListItem';
|
||||||
|
import useWsInventorySources from './useWsInventorySources';
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('inventory', {
|
const QS_CONFIG = getQSConfig('inventory', {
|
||||||
not__source: '',
|
not__source: '',
|
||||||
@@ -34,7 +35,7 @@ function InventorySourceList({ i18n }) {
|
|||||||
const {
|
const {
|
||||||
isLoading,
|
isLoading,
|
||||||
error: fetchError,
|
error: fetchError,
|
||||||
result: { sources, sourceCount, sourceChoices, sourceChoicesOptions },
|
result: { result, sourceCount, sourceChoices, sourceChoicesOptions },
|
||||||
request: fetchSources,
|
request: fetchSources,
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
@@ -44,18 +45,21 @@ function InventorySourceList({ i18n }) {
|
|||||||
InventorySourcesAPI.readOptions(),
|
InventorySourcesAPI.readOptions(),
|
||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
sources: results[0].data.results,
|
result: results[0].data.results,
|
||||||
sourceCount: results[0].data.count,
|
sourceCount: results[0].data.count,
|
||||||
sourceChoices: results[1].data.actions.GET.source.choices,
|
sourceChoices: results[1].data.actions.GET.source.choices,
|
||||||
sourceChoicesOptions: results[1].data.actions,
|
sourceChoicesOptions: results[1].data.actions,
|
||||||
};
|
};
|
||||||
}, [id, search]),
|
}, [id, search]),
|
||||||
{
|
{
|
||||||
sources: [],
|
result: [],
|
||||||
sourceCount: 0,
|
sourceCount: 0,
|
||||||
sourceChoices: [],
|
sourceChoices: [],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sources = useWsInventorySources(result);
|
||||||
|
|
||||||
const canSyncSources =
|
const canSyncSources =
|
||||||
sources.length > 0 &&
|
sources.length > 0 &&
|
||||||
sources.every(source => source.summary_fields.user_capabilities.start);
|
sources.every(source => source.summary_fields.user_capabilities.start);
|
||||||
|
|||||||
@@ -55,8 +55,11 @@ const sources = {
|
|||||||
describe('<InventorySourceList />', () => {
|
describe('<InventorySourceList />', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let history;
|
let history;
|
||||||
|
let debug;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
debug = global.console.debug; // eslint-disable-line prefer-destructuring
|
||||||
|
global.console.debug = () => {};
|
||||||
InventoriesAPI.readSources.mockResolvedValue(sources);
|
InventoriesAPI.readSources.mockResolvedValue(sources);
|
||||||
InventorySourcesAPI.readOptions.mockResolvedValue({
|
InventorySourcesAPI.readOptions.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
@@ -98,6 +101,7 @@ describe('<InventorySourceList />', () => {
|
|||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
global.console.debug = debug;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should mount properly', async () => {
|
test('should mount properly', async () => {
|
||||||
|
|||||||
@@ -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)];
|
||||||
|
}
|
||||||
@@ -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 don’t 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user