From 1dcb3afa9572926c7124c2999a205ee838ecaba0 Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Wed, 13 Mar 2024 12:52:54 +0100 Subject: [PATCH 01/16] Add Redux Sagas for getting SUSE Manager software updates --- assets/js/lib/api/softwareUpdates.js | 4 + assets/js/state/sagas/softwareUpdates.js | 28 +++ assets/js/state/sagas/softwareUpdates.test.js | 88 +++++++++ assets/js/state/softwareUpdates.js | 44 +++++ assets/js/state/softwareUpdates.test.js | 174 ++++++++++++++++++ 5 files changed, 338 insertions(+) create mode 100644 assets/js/lib/api/softwareUpdates.js create mode 100644 assets/js/state/sagas/softwareUpdates.js create mode 100644 assets/js/state/sagas/softwareUpdates.test.js create mode 100644 assets/js/state/softwareUpdates.js create mode 100644 assets/js/state/softwareUpdates.test.js diff --git a/assets/js/lib/api/softwareUpdates.js b/assets/js/lib/api/softwareUpdates.js new file mode 100644 index 0000000000..7462b87bff --- /dev/null +++ b/assets/js/lib/api/softwareUpdates.js @@ -0,0 +1,4 @@ +import { networkClient } from '@lib/network'; + +export const getSoftwareUpdates = (hostId) => + networkClient.get(`/api/v1/hosts/${hostId}/software_updates`); diff --git a/assets/js/state/sagas/softwareUpdates.js b/assets/js/state/sagas/softwareUpdates.js new file mode 100644 index 0000000000..4d36d60c62 --- /dev/null +++ b/assets/js/state/sagas/softwareUpdates.js @@ -0,0 +1,28 @@ +import { get } from 'lodash'; +import { put, call, takeEvery } from 'redux-saga/effects'; +import { getSoftwareUpdates } from '@lib/api/softwareUpdates'; + +import { + FETCH_SOFTWARE_UPDATES, + startLoadingSoftwareUpdates, + setSoftwareUpdates, + setEmptySoftwareUpdates, + setSoftwareUpdatesErrors, +} from '@state/softwareUpdates'; + +export function* fetchSoftwareUpdates({ payload: { hostId } }) { + yield put(startLoadingSoftwareUpdates()); + + try { + const response = yield call(getSoftwareUpdates, hostId); + yield put(setSoftwareUpdates({ hostId, softwareUpdates: response.data })); + } catch (error) { + yield put(setEmptySoftwareUpdates()); + const errors = get(error, ['response', 'data'], []); + yield put(setSoftwareUpdatesErrors(errors)); + } +} + +export function* watchSoftwareUpdates() { + yield takeEvery(FETCH_SOFTWARE_UPDATES, fetchSoftwareUpdates); +} diff --git a/assets/js/state/sagas/softwareUpdates.test.js b/assets/js/state/sagas/softwareUpdates.test.js new file mode 100644 index 0000000000..5382897277 --- /dev/null +++ b/assets/js/state/sagas/softwareUpdates.test.js @@ -0,0 +1,88 @@ +import { faker } from '@faker-js/faker'; + +import { recordSaga } from '@lib/test-utils'; + +import { networkClient } from '@lib/network'; +import MockAdapter from 'axios-mock-adapter'; + +import { + startLoadingSoftwareUpdates, + setSoftwareUpdates, + setSoftwareUpdatesErrors, + setEmptySoftwareUpdates, +} from '@state/softwareUpdates'; + +import { fetchSoftwareUpdates } from './softwareUpdates'; + +describe('Software Updates saga', () => { + describe('Fetching Software Updates', () => { + it('should successfully fetch software updates', async () => { + const axiosMock = new MockAdapter(networkClient); + const hostId = faker.string.uuid(); + const successfulResponse = [ + { + date: '2023-05-18', + advisory_name: 'SUSE-15-SP4-2023-2245', + advisory_type: 'Bug Fix Advisory', + advisory_status: 'stable', + id: 2192, + advisory_synopsis: 'Recommended update for libzypp, zypper', + update_date: '2023-05-18', + }, + { + from_epoch: ' ', + to_release: '150400.7.60.2', + name: 'openssl-1_1', + from_release: '150400.7.25.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 37454, + from_version: '1.1.1l', + to_version: '1.1.1l', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + ]; + + axiosMock + .onGet(`/api/v1/hosts/${hostId}/software_updates`) + .reply(200, successfulResponse); + + const dispatched = await recordSaga(fetchSoftwareUpdates, { + payload: { hostId }, + }); + + expect(dispatched).toEqual([ + startLoadingSoftwareUpdates(), + setSoftwareUpdates({ hostId, softwareUpdates: successfulResponse }), + ]); + }); + + it('should empty software updates settings on failed fetching', async () => { + const axiosMock = new MockAdapter(networkClient); + const hostId = faker.string.uuid(); + const responses = [ + { status: 404, body: { message: '404 Not found' } }, + { status: 500, body: { message: 'java.lang.NullPointerException' } }, + ]; + + /* eslint-disable no-await-in-loop */ + /* eslint-disable no-restricted-syntax */ + for (const { status, body } of responses) { + axiosMock + .onGet(`/api/v1/hosts/${hostId}/software_updates`) + .reply(status, body); + + const dispatched = await recordSaga(fetchSoftwareUpdates, { + payload: { hostId }, + }); + + expect(dispatched).toEqual([ + startLoadingSoftwareUpdates(), + setEmptySoftwareUpdates(), + setSoftwareUpdatesErrors(body), + ]); + } + }); + }); +}); diff --git a/assets/js/state/softwareUpdates.js b/assets/js/state/softwareUpdates.js new file mode 100644 index 0000000000..6a5b029a92 --- /dev/null +++ b/assets/js/state/softwareUpdates.js @@ -0,0 +1,44 @@ +import { createAction, createSlice } from '@reduxjs/toolkit'; + +const initialState = { + loading: false, + softwareUpdates: [], + errors: [], +}; + +export const softwareUpdatesSlice = createSlice({ + name: 'softwareUpdates', + initialState, + reducers: { + startLoadingSoftwareUpdates: (state) => { + state.loading = true; + }, + setSoftwareUpdates: (state, { payload: { hostId, softwareUpdates } }) => { + state.loading = false; + state.softwareUpdates = state.softwareUpdates + .filter((su) => su.hostId !== hostId) + .concat({ hostId, softwareUpdates }); + }, + setEmptySoftwareUpdates: (state) => { + state.loading = false; + state.softwareUpdates = []; + }, + setSoftwareUpdatesErrors: (state, { payload: errors }) => { + state.loading = false; + state.errors = errors; + }, + }, +}); + +export const FETCH_SOFTWARE_UPDATES = 'FETCH_SOFTWARE_UPDATES'; + +export const fetchSoftwareUpdates = createAction(FETCH_SOFTWARE_UPDATES); + +export const { + startLoadingSoftwareUpdates, + setSoftwareUpdates, + setEmptySoftwareUpdates, + setSoftwareUpdatesErrors, +} = softwareUpdatesSlice.actions; + +export default softwareUpdatesSlice.reducer; diff --git a/assets/js/state/softwareUpdates.test.js b/assets/js/state/softwareUpdates.test.js new file mode 100644 index 0000000000..86130fed99 --- /dev/null +++ b/assets/js/state/softwareUpdates.test.js @@ -0,0 +1,174 @@ +import softwareUpdatesReducer, { + startLoadingSoftwareUpdates, + setSoftwareUpdates, + setEmptySoftwareUpdates, + setSoftwareUpdatesErrors, +} from './softwareUpdates'; + +describe('SoftwareUpdates reducer', () => { + it('should mark retrieval of software updates in loading state', () => { + const initialState = { + loading: false, + }; + + const action = startLoadingSoftwareUpdates(); + + const expectedState = { + loading: true, + }; + + expect(softwareUpdatesReducer(initialState, action)).toEqual(expectedState); + }); + + it('should set software updates', () => { + const initialState = { + loading: true, + softwareUpdates: [ + { hostId: 'host1', softwareUpdates: [] }, + { + hostId: 'host2', + softwareUpdates: [ + { + date: '2024-03-11', + advisory_name: 'SUSE-15-SP4-2024-833', + advisory_type: 'Security Advisory', + advisory_status: 'stable', + id: 4244, + advisory_synopsis: 'moderate: Security update for openssl-1_1', + update_date: '2024-03-11', + }, + { + from_epoch: ' ', + to_release: '150100.8.33.1', + name: 'saptune', + from_release: '150400.3.208.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 39942, + from_version: '3.1.0', + to_version: '3.1.2', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + ], + }, + ], + errors: [], + }; + + const newSoftwareUpdates = [ + { + from_epoch: ' ', + to_release: '150300.3.30.1', + name: 'openssh-server', + from_release: '150300.3.15.4', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 39543, + from_version: '8.4p1', + to_version: '8.4p1', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + { + date: '2023-05-30', + advisory_name: 'SUSE-15-SP4-2023-2317', + advisory_type: 'Bug Fix Advisory', + advisory_status: 'stable', + id: 2226, + advisory_synopsis: 'Recommended update for util-linux', + update_date: '2023-05-30', + }, + ]; + + const action = setSoftwareUpdates({ + hostId: 'host2', + softwareUpdates: newSoftwareUpdates, + }); + + const actual = softwareUpdatesReducer(initialState, action); + + expect(actual).toEqual({ + loading: false, + softwareUpdates: [ + { hostId: 'host1', softwareUpdates: [] }, + { hostId: 'host2', softwareUpdates: newSoftwareUpdates }, + ], + errors: [], + }); + }); + + it('should empty software updates', () => { + const initialState = { + loading: true, + softwareUpdates: [ + { hostId: 'host1', softwareUpdates: [] }, + { hostId: 'host2', softwareUpdates: [] }, + ], + errors: [], + }; + + const action = setEmptySoftwareUpdates(); + + const actual = softwareUpdatesReducer(initialState, action); + + expect(actual).toEqual({ + loading: false, + softwareUpdates: [], + errors: [], + }); + }); + + it('should set errors upon if error occurred', () => { + const initialState = { + loading: true, + softwareUpdates: [ + { + hostId: 'host1', + softwareUpdates: [ + { + date: '2024-03-11', + advisory_name: 'SUSE-15-SP4-2024-833', + advisory_type: 'Security Advisory', + advisory_status: 'stable', + id: 4244, + advisory_synopsis: 'moderate: Security update for openssl-1_1', + update_date: '2024-03-11', + }, + { + from_epoch: ' ', + to_release: '150100.8.33.1', + name: 'saptune', + from_release: '150400.3.208.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 39942, + from_version: '3.1.0', + to_version: '3.1.2', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + ], + }, + ], + errors: [], + }; + + const errors = [ + { + detail: 'some error occurred while retrieving software updates', + title: 'An error occurred', + }, + { + detail: 'another error has also occurred', + title: 'Another error occured', + }, + ]; + + const action = setSoftwareUpdatesErrors(errors); + + const actual = softwareUpdatesReducer(initialState, action); + + expect(actual).toEqual({ ...initialState, loading: false, errors }); + }); +}); From 244391856316214dfe330aa8769ac3bbb57c56fc Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Wed, 13 Mar 2024 14:57:32 +0100 Subject: [PATCH 02/16] Update to match API --- assets/js/state/sagas/softwareUpdates.js | 2 +- assets/js/state/sagas/softwareUpdates.test.js | 56 +++++------ assets/js/state/softwareUpdates.js | 20 ++-- assets/js/state/softwareUpdates.test.js | 92 ++++++++++++------- 4 files changed, 106 insertions(+), 64 deletions(-) diff --git a/assets/js/state/sagas/softwareUpdates.js b/assets/js/state/sagas/softwareUpdates.js index 4d36d60c62..e94d80a828 100644 --- a/assets/js/state/sagas/softwareUpdates.js +++ b/assets/js/state/sagas/softwareUpdates.js @@ -15,7 +15,7 @@ export function* fetchSoftwareUpdates({ payload: { hostId } }) { try { const response = yield call(getSoftwareUpdates, hostId); - yield put(setSoftwareUpdates({ hostId, softwareUpdates: response.data })); + yield put(setSoftwareUpdates({ hostId, ...response.data })); } catch (error) { yield put(setEmptySoftwareUpdates()); const errors = get(error, ['response', 'data'], []); diff --git a/assets/js/state/sagas/softwareUpdates.test.js b/assets/js/state/sagas/softwareUpdates.test.js index 5382897277..b892814606 100644 --- a/assets/js/state/sagas/softwareUpdates.test.js +++ b/assets/js/state/sagas/softwareUpdates.test.js @@ -19,42 +19,46 @@ describe('Software Updates saga', () => { it('should successfully fetch software updates', async () => { const axiosMock = new MockAdapter(networkClient); const hostId = faker.string.uuid(); - const successfulResponse = [ - { - date: '2023-05-18', - advisory_name: 'SUSE-15-SP4-2023-2245', - advisory_type: 'Bug Fix Advisory', - advisory_status: 'stable', - id: 2192, - advisory_synopsis: 'Recommended update for libzypp, zypper', - update_date: '2023-05-18', - }, - { - from_epoch: ' ', - to_release: '150400.7.60.2', - name: 'openssl-1_1', - from_release: '150400.7.25.1', - to_epoch: ' ', - arch: 'x86_64', - to_package_id: 37454, - from_version: '1.1.1l', - to_version: '1.1.1l', - from_arch: 'x86_64', - to_arch: 'x86_64', - }, - ]; + const successfulResponse = { + relevant_patches: [ + { + date: '2023-05-18', + advisory_name: 'SUSE-15-SP4-2023-2245', + advisory_type: 'Bug Fix Advisory', + advisory_status: 'stable', + id: 2192, + advisory_synopsis: 'Recommended update for libzypp, zypper', + update_date: '2023-05-18', + }, + ], + software_updates: [ + { + from_epoch: ' ', + to_release: '150400.7.60.2', + name: 'openssl-1_1', + from_release: '150400.7.25.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 37454, + from_version: '1.1.1l', + to_version: '1.1.1l', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + ], + }; axiosMock .onGet(`/api/v1/hosts/${hostId}/software_updates`) .reply(200, successfulResponse); const dispatched = await recordSaga(fetchSoftwareUpdates, { - payload: { hostId }, + payload: { hostId, ...successfulResponse }, }); expect(dispatched).toEqual([ startLoadingSoftwareUpdates(), - setSoftwareUpdates({ hostId, softwareUpdates: successfulResponse }), + setSoftwareUpdates({ hostId, ...successfulResponse }), ]); }); diff --git a/assets/js/state/softwareUpdates.js b/assets/js/state/softwareUpdates.js index 6a5b029a92..f8cdb05f0a 100644 --- a/assets/js/state/softwareUpdates.js +++ b/assets/js/state/softwareUpdates.js @@ -2,7 +2,7 @@ import { createAction, createSlice } from '@reduxjs/toolkit'; const initialState = { loading: false, - softwareUpdates: [], + softwareUpdates: {}, errors: [], }; @@ -13,15 +13,23 @@ export const softwareUpdatesSlice = createSlice({ startLoadingSoftwareUpdates: (state) => { state.loading = true; }, - setSoftwareUpdates: (state, { payload: { hostId, softwareUpdates } }) => { + setSoftwareUpdates: ( + state, + { payload: { hostId, relevant_patches, software_updates } } + ) => { state.loading = false; - state.softwareUpdates = state.softwareUpdates - .filter((su) => su.hostId !== hostId) - .concat({ hostId, softwareUpdates }); + + state.softwareUpdates = { + ...state.softwareUpdates, + [hostId]: { + relevant_patches, + software_updates, + }, + }; }, setEmptySoftwareUpdates: (state) => { state.loading = false; - state.softwareUpdates = []; + state.softwareUpdates = {}; }, setSoftwareUpdatesErrors: (state, { payload: errors }) => { state.loading = false; diff --git a/assets/js/state/softwareUpdates.test.js b/assets/js/state/softwareUpdates.test.js index 86130fed99..0f14ebdfff 100644 --- a/assets/js/state/softwareUpdates.test.js +++ b/assets/js/state/softwareUpdates.test.js @@ -23,11 +23,13 @@ describe('SoftwareUpdates reducer', () => { it('should set software updates', () => { const initialState = { loading: true, - softwareUpdates: [ - { hostId: 'host1', softwareUpdates: [] }, - { - hostId: 'host2', - softwareUpdates: [ + softwareUpdates: { + host1: { + relevant_patches: [], + software_updates: [], + }, + host2: { + relevant_patches: [ { date: '2024-03-11', advisory_name: 'SUSE-15-SP4-2024-833', @@ -37,6 +39,8 @@ describe('SoftwareUpdates reducer', () => { advisory_synopsis: 'moderate: Security update for openssl-1_1', update_date: '2024-03-11', }, + ], + software_updates: [ { from_epoch: ' ', to_release: '150100.8.33.1', @@ -52,10 +56,22 @@ describe('SoftwareUpdates reducer', () => { }, ], }, - ], + }, errors: [], }; + const newRelevantPatches = [ + { + date: '2023-05-30', + advisory_name: 'SUSE-15-SP4-2023-2317', + advisory_type: 'Bug Fix Advisory', + advisory_status: 'stable', + id: 2226, + advisory_synopsis: 'Recommended update for util-linux', + update_date: '2023-05-30', + }, + ]; + const newSoftwareUpdates = [ { from_epoch: ' ', @@ -70,30 +86,30 @@ describe('SoftwareUpdates reducer', () => { from_arch: 'x86_64', to_arch: 'x86_64', }, - { - date: '2023-05-30', - advisory_name: 'SUSE-15-SP4-2023-2317', - advisory_type: 'Bug Fix Advisory', - advisory_status: 'stable', - id: 2226, - advisory_synopsis: 'Recommended update for util-linux', - update_date: '2023-05-30', - }, ]; + const newSoftwareUpdatesState = { + relevant_patches: newRelevantPatches, + software_updates: newSoftwareUpdates, + }; + const action = setSoftwareUpdates({ hostId: 'host2', - softwareUpdates: newSoftwareUpdates, + relevant_patches: newRelevantPatches, + software_updates: newSoftwareUpdates, }); const actual = softwareUpdatesReducer(initialState, action); expect(actual).toEqual({ loading: false, - softwareUpdates: [ - { hostId: 'host1', softwareUpdates: [] }, - { hostId: 'host2', softwareUpdates: newSoftwareUpdates }, - ], + softwareUpdates: { + host1: { + relevant_patches: [], + software_updates: [], + }, + host2: newSoftwareUpdatesState, + }, errors: [], }); }); @@ -101,10 +117,23 @@ describe('SoftwareUpdates reducer', () => { it('should empty software updates', () => { const initialState = { loading: true, - softwareUpdates: [ - { hostId: 'host1', softwareUpdates: [] }, - { hostId: 'host2', softwareUpdates: [] }, - ], + softwareUpdates: { + host1: { relevant_patches: [], software_updates: [] }, + host2: { + relevant_patches: [ + { + date: '2023-03-22', + advisory_name: 'SUSE-15-SP4-2023-868', + advisory_type: 'Security Advisory', + advisory_status: 'stable', + id: 2136, + advisory_synopsis: 'important: Security update for python3', + update_date: '2023-03-22', + }, + ], + software_updates: [], + }, + }, errors: [], }; @@ -114,7 +143,7 @@ describe('SoftwareUpdates reducer', () => { expect(actual).toEqual({ loading: false, - softwareUpdates: [], + softwareUpdates: {}, errors: [], }); }); @@ -122,10 +151,9 @@ describe('SoftwareUpdates reducer', () => { it('should set errors upon if error occurred', () => { const initialState = { loading: true, - softwareUpdates: [ - { - hostId: 'host1', - softwareUpdates: [ + softwareUpdates: { + host1: { + relevant_patches: [ { date: '2024-03-11', advisory_name: 'SUSE-15-SP4-2024-833', @@ -135,6 +163,8 @@ describe('SoftwareUpdates reducer', () => { advisory_synopsis: 'moderate: Security update for openssl-1_1', update_date: '2024-03-11', }, + ], + software_updates: [ { from_epoch: ' ', to_release: '150100.8.33.1', @@ -150,7 +180,7 @@ describe('SoftwareUpdates reducer', () => { }, ], }, - ], + }, errors: [], }; @@ -161,7 +191,7 @@ describe('SoftwareUpdates reducer', () => { }, { detail: 'another error has also occurred', - title: 'Another error occured', + title: 'Another error occurred', }, ]; From b3fbb679aaf62b48ba06af689b26d2d0c52bbdf8 Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Wed, 13 Mar 2024 15:10:48 +0100 Subject: [PATCH 03/16] Register Saga-watcher in root Saga --- assets/js/state/sagas/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assets/js/state/sagas/index.js b/assets/js/state/sagas/index.js index 8f745622d7..cd9e485aae 100644 --- a/assets/js/state/sagas/index.js +++ b/assets/js/state/sagas/index.js @@ -73,6 +73,7 @@ import { watchSapSystemEvents } from '@state/sagas/sapSystems'; import { watchPerformLogin } from '@state/sagas/user'; import { watchChecksSelectionEvents } from '@state/sagas/checksSelection'; import { watchSoftwareUpdateSettings } from '@state/sagas/softwareUpdatesSettings'; +import { watchSoftwareUpdates } from '@state/sagas/softwareUpdates'; import { initSocketConnection } from '@lib/network/socket'; import processChannelEvents from '@state/channels'; @@ -248,5 +249,6 @@ export default function* rootSaga() { watchSapSystemEvents(), watchUserLoggedIn(), watchSoftwareUpdateSettings(), + watchSoftwareUpdates(), ]); } From 91c0cc39154663d02bdc740a6443b6339ad7b3a2 Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Wed, 13 Mar 2024 15:30:02 +0100 Subject: [PATCH 04/16] Only remove a specific host instead of clearing entire state --- assets/js/state/sagas/softwareUpdates.js | 2 +- assets/js/state/sagas/softwareUpdates.test.js | 2 +- assets/js/state/softwareUpdates.js | 8 ++++++-- assets/js/state/softwareUpdates.test.js | 6 ++++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/assets/js/state/sagas/softwareUpdates.js b/assets/js/state/sagas/softwareUpdates.js index e94d80a828..f06cfecd29 100644 --- a/assets/js/state/sagas/softwareUpdates.js +++ b/assets/js/state/sagas/softwareUpdates.js @@ -17,7 +17,7 @@ export function* fetchSoftwareUpdates({ payload: { hostId } }) { const response = yield call(getSoftwareUpdates, hostId); yield put(setSoftwareUpdates({ hostId, ...response.data })); } catch (error) { - yield put(setEmptySoftwareUpdates()); + yield put(setEmptySoftwareUpdates({ hostId })); const errors = get(error, ['response', 'data'], []); yield put(setSoftwareUpdatesErrors(errors)); } diff --git a/assets/js/state/sagas/softwareUpdates.test.js b/assets/js/state/sagas/softwareUpdates.test.js index b892814606..c270b057e4 100644 --- a/assets/js/state/sagas/softwareUpdates.test.js +++ b/assets/js/state/sagas/softwareUpdates.test.js @@ -83,7 +83,7 @@ describe('Software Updates saga', () => { expect(dispatched).toEqual([ startLoadingSoftwareUpdates(), - setEmptySoftwareUpdates(), + setEmptySoftwareUpdates({ hostId }), setSoftwareUpdatesErrors(body), ]); } diff --git a/assets/js/state/softwareUpdates.js b/assets/js/state/softwareUpdates.js index f8cdb05f0a..433447553a 100644 --- a/assets/js/state/softwareUpdates.js +++ b/assets/js/state/softwareUpdates.js @@ -27,9 +27,13 @@ export const softwareUpdatesSlice = createSlice({ }, }; }, - setEmptySoftwareUpdates: (state) => { + setEmptySoftwareUpdates: (state, { payload: { hostId } }) => { state.loading = false; - state.softwareUpdates = {}; + state.softwareUpdates = Object.fromEntries( + Object.entries(state.softwareUpdates).filter( + ([hostIdKey]) => hostIdKey !== hostId + ) + ); }, setSoftwareUpdatesErrors: (state, { payload: errors }) => { state.loading = false; diff --git a/assets/js/state/softwareUpdates.test.js b/assets/js/state/softwareUpdates.test.js index 0f14ebdfff..5afbbb1eef 100644 --- a/assets/js/state/softwareUpdates.test.js +++ b/assets/js/state/softwareUpdates.test.js @@ -137,13 +137,15 @@ describe('SoftwareUpdates reducer', () => { errors: [], }; - const action = setEmptySoftwareUpdates(); + const action = setEmptySoftwareUpdates({ hostId: 'host2' }); const actual = softwareUpdatesReducer(initialState, action); expect(actual).toEqual({ loading: false, - softwareUpdates: {}, + softwareUpdates: { + host1: { relevant_patches: [], software_updates: [] }, + }, errors: [], }); }); From a11b9cfb5e44fccbe3f08bd43891b0e3679c8c06 Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Wed, 13 Mar 2024 15:55:48 +0100 Subject: [PATCH 05/16] Update mocked API responses --- assets/js/state/sagas/softwareUpdates.test.js | 2 +- assets/js/state/softwareUpdates.test.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/js/state/sagas/softwareUpdates.test.js b/assets/js/state/sagas/softwareUpdates.test.js index c270b057e4..b758d2ae1a 100644 --- a/assets/js/state/sagas/softwareUpdates.test.js +++ b/assets/js/state/sagas/softwareUpdates.test.js @@ -24,7 +24,7 @@ describe('Software Updates saga', () => { { date: '2023-05-18', advisory_name: 'SUSE-15-SP4-2023-2245', - advisory_type: 'Bug Fix Advisory', + advisory_type: 'bugfix', advisory_status: 'stable', id: 2192, advisory_synopsis: 'Recommended update for libzypp, zypper', diff --git a/assets/js/state/softwareUpdates.test.js b/assets/js/state/softwareUpdates.test.js index 5afbbb1eef..28c6a38456 100644 --- a/assets/js/state/softwareUpdates.test.js +++ b/assets/js/state/softwareUpdates.test.js @@ -33,7 +33,7 @@ describe('SoftwareUpdates reducer', () => { { date: '2024-03-11', advisory_name: 'SUSE-15-SP4-2024-833', - advisory_type: 'Security Advisory', + advisory_type: 'security_advisory', advisory_status: 'stable', id: 4244, advisory_synopsis: 'moderate: Security update for openssl-1_1', @@ -64,7 +64,7 @@ describe('SoftwareUpdates reducer', () => { { date: '2023-05-30', advisory_name: 'SUSE-15-SP4-2023-2317', - advisory_type: 'Bug Fix Advisory', + advisory_type: 'bugfix', advisory_status: 'stable', id: 2226, advisory_synopsis: 'Recommended update for util-linux', @@ -124,7 +124,7 @@ describe('SoftwareUpdates reducer', () => { { date: '2023-03-22', advisory_name: 'SUSE-15-SP4-2023-868', - advisory_type: 'Security Advisory', + advisory_type: 'security_advisory', advisory_status: 'stable', id: 2136, advisory_synopsis: 'important: Security update for python3', @@ -159,7 +159,7 @@ describe('SoftwareUpdates reducer', () => { { date: '2024-03-11', advisory_name: 'SUSE-15-SP4-2024-833', - advisory_type: 'Security Advisory', + advisory_type: 'security_advisory', advisory_status: 'stable', id: 4244, advisory_synopsis: 'moderate: Security update for openssl-1_1', From 265a94f3c1fc9604de87ab80d1bb2f43825c6663 Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Wed, 13 Mar 2024 16:10:12 +0100 Subject: [PATCH 06/16] Refactor Sagas tests --- assets/js/state/sagas/softwareUpdates.test.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/assets/js/state/sagas/softwareUpdates.test.js b/assets/js/state/sagas/softwareUpdates.test.js index b758d2ae1a..1bf788f57c 100644 --- a/assets/js/state/sagas/softwareUpdates.test.js +++ b/assets/js/state/sagas/softwareUpdates.test.js @@ -62,17 +62,15 @@ describe('Software Updates saga', () => { ]); }); - it('should empty software updates settings on failed fetching', async () => { - const axiosMock = new MockAdapter(networkClient); - const hostId = faker.string.uuid(); - const responses = [ - { status: 404, body: { message: '404 Not found' } }, - { status: 500, body: { message: 'java.lang.NullPointerException' } }, - ]; + it.each([ + { status: 404, body: { message: '404 Not found' } }, + { status: 500, body: { message: 'java.lang.NullPointerException' } }, + ])( + 'should empty software updates settings on failed fetching', + async ({ status, body }) => { + const axiosMock = new MockAdapter(networkClient); + const hostId = faker.string.uuid(); - /* eslint-disable no-await-in-loop */ - /* eslint-disable no-restricted-syntax */ - for (const { status, body } of responses) { axiosMock .onGet(`/api/v1/hosts/${hostId}/software_updates`) .reply(status, body); @@ -87,6 +85,6 @@ describe('Software Updates saga', () => { setSoftwareUpdatesErrors(body), ]); } - }); + ); }); }); From e36a78f945589561620c86ec2e139e7b3053ea84 Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Wed, 13 Mar 2024 16:33:15 +0100 Subject: [PATCH 07/16] Generate host IDs in tests --- assets/js/state/sagas/softwareUpdates.test.js | 2 +- assets/js/state/softwareUpdates.test.js | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/assets/js/state/sagas/softwareUpdates.test.js b/assets/js/state/sagas/softwareUpdates.test.js index 1bf788f57c..851522c7b4 100644 --- a/assets/js/state/sagas/softwareUpdates.test.js +++ b/assets/js/state/sagas/softwareUpdates.test.js @@ -1,9 +1,9 @@ import { faker } from '@faker-js/faker'; +import MockAdapter from 'axios-mock-adapter'; import { recordSaga } from '@lib/test-utils'; import { networkClient } from '@lib/network'; -import MockAdapter from 'axios-mock-adapter'; import { startLoadingSoftwareUpdates, diff --git a/assets/js/state/softwareUpdates.test.js b/assets/js/state/softwareUpdates.test.js index 28c6a38456..7c00df6555 100644 --- a/assets/js/state/softwareUpdates.test.js +++ b/assets/js/state/softwareUpdates.test.js @@ -1,3 +1,5 @@ +import { faker } from '@faker-js/faker'; + import softwareUpdatesReducer, { startLoadingSoftwareUpdates, setSoftwareUpdates, @@ -21,14 +23,17 @@ describe('SoftwareUpdates reducer', () => { }); it('should set software updates', () => { + const host1 = faker.string.uuid(); + const host2 = faker.string.uuid(); + const initialState = { loading: true, softwareUpdates: { - host1: { + [host1]: { relevant_patches: [], software_updates: [], }, - host2: { + [host2]: { relevant_patches: [ { date: '2024-03-11', @@ -94,7 +99,7 @@ describe('SoftwareUpdates reducer', () => { }; const action = setSoftwareUpdates({ - hostId: 'host2', + hostId: host2, relevant_patches: newRelevantPatches, software_updates: newSoftwareUpdates, }); @@ -104,22 +109,25 @@ describe('SoftwareUpdates reducer', () => { expect(actual).toEqual({ loading: false, softwareUpdates: { - host1: { + [host1]: { relevant_patches: [], software_updates: [], }, - host2: newSoftwareUpdatesState, + [host2]: newSoftwareUpdatesState, }, errors: [], }); }); it('should empty software updates', () => { + const host1 = faker.string.uuid(); + const host2 = faker.string.uuid(); + const initialState = { loading: true, softwareUpdates: { - host1: { relevant_patches: [], software_updates: [] }, - host2: { + [host1]: { relevant_patches: [], software_updates: [] }, + [host2]: { relevant_patches: [ { date: '2023-03-22', @@ -137,24 +145,26 @@ describe('SoftwareUpdates reducer', () => { errors: [], }; - const action = setEmptySoftwareUpdates({ hostId: 'host2' }); + const action = setEmptySoftwareUpdates({ hostId: host2 }); const actual = softwareUpdatesReducer(initialState, action); expect(actual).toEqual({ loading: false, softwareUpdates: { - host1: { relevant_patches: [], software_updates: [] }, + [host1]: { relevant_patches: [], software_updates: [] }, }, errors: [], }); }); it('should set errors upon if error occurred', () => { + const host1 = faker.string.uuid(); + const initialState = { loading: true, softwareUpdates: { - host1: { + [host1]: { relevant_patches: [ { date: '2024-03-11', From d3b35de7ac49765da45d9cfc7726d8aac43747d3 Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Wed, 13 Mar 2024 17:10:38 +0100 Subject: [PATCH 08/16] Rework `setEmptySoftwareUpdates` reducer --- assets/js/state/softwareUpdates.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/js/state/softwareUpdates.js b/assets/js/state/softwareUpdates.js index 433447553a..f29687dc86 100644 --- a/assets/js/state/softwareUpdates.js +++ b/assets/js/state/softwareUpdates.js @@ -29,10 +29,10 @@ export const softwareUpdatesSlice = createSlice({ }, setEmptySoftwareUpdates: (state, { payload: { hostId } }) => { state.loading = false; - state.softwareUpdates = Object.fromEntries( - Object.entries(state.softwareUpdates).filter( - ([hostIdKey]) => hostIdKey !== hostId - ) + state.softwareUpdates = Object.entries(state.softwareUpdates).reduce( + (newState, [id, updates]) => + id !== hostId ? { ...newState, [id]: updates } : newState, + {} ); }, setSoftwareUpdatesErrors: (state, { payload: errors }) => { From ef121f6ed93fa0f8915e074da101062a86e2188e Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Thu, 14 Mar 2024 09:31:33 +0100 Subject: [PATCH 09/16] Rename `software_updates` -> `upgradable_packages` --- assets/js/state/sagas/softwareUpdates.test.js | 10 ++--- assets/js/state/softwareUpdates.js | 4 +- assets/js/state/softwareUpdates.test.js | 38 +++++++------------ 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/assets/js/state/sagas/softwareUpdates.test.js b/assets/js/state/sagas/softwareUpdates.test.js index 851522c7b4..16052e3c17 100644 --- a/assets/js/state/sagas/softwareUpdates.test.js +++ b/assets/js/state/sagas/softwareUpdates.test.js @@ -19,7 +19,7 @@ describe('Software Updates saga', () => { it('should successfully fetch software updates', async () => { const axiosMock = new MockAdapter(networkClient); const hostId = faker.string.uuid(); - const successfulResponse = { + const response = { relevant_patches: [ { date: '2023-05-18', @@ -31,7 +31,7 @@ describe('Software Updates saga', () => { update_date: '2023-05-18', }, ], - software_updates: [ + upgradable_packages: [ { from_epoch: ' ', to_release: '150400.7.60.2', @@ -50,15 +50,15 @@ describe('Software Updates saga', () => { axiosMock .onGet(`/api/v1/hosts/${hostId}/software_updates`) - .reply(200, successfulResponse); + .reply(200, response); const dispatched = await recordSaga(fetchSoftwareUpdates, { - payload: { hostId, ...successfulResponse }, + payload: { hostId, ...response }, }); expect(dispatched).toEqual([ startLoadingSoftwareUpdates(), - setSoftwareUpdates({ hostId, ...successfulResponse }), + setSoftwareUpdates({ hostId, ...response }), ]); }); diff --git a/assets/js/state/softwareUpdates.js b/assets/js/state/softwareUpdates.js index f29687dc86..d15475c08f 100644 --- a/assets/js/state/softwareUpdates.js +++ b/assets/js/state/softwareUpdates.js @@ -15,7 +15,7 @@ export const softwareUpdatesSlice = createSlice({ }, setSoftwareUpdates: ( state, - { payload: { hostId, relevant_patches, software_updates } } + { payload: { hostId, relevant_patches, upgradable_packages } } ) => { state.loading = false; @@ -23,7 +23,7 @@ export const softwareUpdatesSlice = createSlice({ ...state.softwareUpdates, [hostId]: { relevant_patches, - software_updates, + upgradable_packages, }, }; }, diff --git a/assets/js/state/softwareUpdates.test.js b/assets/js/state/softwareUpdates.test.js index 7c00df6555..81b1ce5d2b 100644 --- a/assets/js/state/softwareUpdates.test.js +++ b/assets/js/state/softwareUpdates.test.js @@ -9,15 +9,11 @@ import softwareUpdatesReducer, { describe('SoftwareUpdates reducer', () => { it('should mark retrieval of software updates in loading state', () => { - const initialState = { - loading: false, - }; + const initialState = { loading: false }; const action = startLoadingSoftwareUpdates(); - const expectedState = { - loading: true, - }; + const expectedState = { loading: true }; expect(softwareUpdatesReducer(initialState, action)).toEqual(expectedState); }); @@ -29,10 +25,7 @@ describe('SoftwareUpdates reducer', () => { const initialState = { loading: true, softwareUpdates: { - [host1]: { - relevant_patches: [], - software_updates: [], - }, + [host1]: { relevant_patches: [], upgradable_packages: [] }, [host2]: { relevant_patches: [ { @@ -45,7 +38,7 @@ describe('SoftwareUpdates reducer', () => { update_date: '2024-03-11', }, ], - software_updates: [ + upgradable_packages: [ { from_epoch: ' ', to_release: '150100.8.33.1', @@ -77,7 +70,7 @@ describe('SoftwareUpdates reducer', () => { }, ]; - const newSoftwareUpdates = [ + const newUpgradablePackages = [ { from_epoch: ' ', to_release: '150300.3.30.1', @@ -93,15 +86,15 @@ describe('SoftwareUpdates reducer', () => { }, ]; - const newSoftwareUpdatesState = { + const newSoftwareUpdates = { relevant_patches: newRelevantPatches, - software_updates: newSoftwareUpdates, + upgradable_packages: newUpgradablePackages, }; const action = setSoftwareUpdates({ hostId: host2, relevant_patches: newRelevantPatches, - software_updates: newSoftwareUpdates, + upgradable_packages: newUpgradablePackages, }); const actual = softwareUpdatesReducer(initialState, action); @@ -109,11 +102,8 @@ describe('SoftwareUpdates reducer', () => { expect(actual).toEqual({ loading: false, softwareUpdates: { - [host1]: { - relevant_patches: [], - software_updates: [], - }, - [host2]: newSoftwareUpdatesState, + [host1]: { relevant_patches: [], upgradable_packages: [] }, + [host2]: newSoftwareUpdates, }, errors: [], }); @@ -126,7 +116,7 @@ describe('SoftwareUpdates reducer', () => { const initialState = { loading: true, softwareUpdates: { - [host1]: { relevant_patches: [], software_updates: [] }, + [host1]: { relevant_patches: [], upgradable_packages: [] }, [host2]: { relevant_patches: [ { @@ -139,7 +129,7 @@ describe('SoftwareUpdates reducer', () => { update_date: '2023-03-22', }, ], - software_updates: [], + upgradable_packages: [], }, }, errors: [], @@ -152,7 +142,7 @@ describe('SoftwareUpdates reducer', () => { expect(actual).toEqual({ loading: false, softwareUpdates: { - [host1]: { relevant_patches: [], software_updates: [] }, + [host1]: { relevant_patches: [], upgradable_packages: [] }, }, errors: [], }); @@ -176,7 +166,7 @@ describe('SoftwareUpdates reducer', () => { update_date: '2024-03-11', }, ], - software_updates: [ + upgradable_packages: [ { from_epoch: ' ', to_release: '150100.8.33.1', From e2e3f1387a07b9f62cafd7e29375ad2ceb4a25c1 Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Thu, 14 Mar 2024 14:56:09 +0100 Subject: [PATCH 10/16] Add Selectors for software update stats --- assets/js/state/selectors/softwareUpdates.js | 12 ++ .../state/selectors/softwareUpdates.test.js | 106 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 assets/js/state/selectors/softwareUpdates.js create mode 100644 assets/js/state/selectors/softwareUpdates.test.js diff --git a/assets/js/state/selectors/softwareUpdates.js b/assets/js/state/selectors/softwareUpdates.js new file mode 100644 index 0000000000..4a2a861611 --- /dev/null +++ b/assets/js/state/selectors/softwareUpdates.js @@ -0,0 +1,12 @@ +import { createSelector } from '@reduxjs/toolkit'; + +export const getSoftwareUpdatesForHost = (id) => (state) => + state?.softwareUpdates[id]; + +export const getSoftwareUpdatesStats = createSelector( + [(state, id) => getSoftwareUpdatesForHost(id)(state)], + (softwareUpdates) => ({ + numRelevantPatches: softwareUpdates?.relevant_patches?.length, + numUpgradablePackages: softwareUpdates?.upgradable_packages?.length, + }) +); diff --git a/assets/js/state/selectors/softwareUpdates.test.js b/assets/js/state/selectors/softwareUpdates.test.js new file mode 100644 index 0000000000..edb63f30b7 --- /dev/null +++ b/assets/js/state/selectors/softwareUpdates.test.js @@ -0,0 +1,106 @@ +import { faker } from '@faker-js/faker'; +import { getSoftwareUpdatesStats } from './softwareUpdates'; + +describe('Software Updates selector', () => { + const hostId = faker.string.uuid(); + const state = { + softwareUpdates: { + [hostId]: { + relevant_patches: [ + { + date: '2024-03-13', + advisory_name: 'SUSE-15-SP4-2024-877', + advisory_type: 'security_advisory', + advisory_status: 'stable', + id: 4263, + advisory_synopsis: 'important: Security update for sudo', + update_date: '2024-03-13', + }, + { + date: '2024-03-13', + advisory_name: 'SUSE-15-SP4-2024-871', + advisory_type: 'security_advisory', + advisory_status: 'stable', + id: 4261, + advisory_synopsis: 'important: Security update for vim', + update_date: '2024-03-13', + }, + { + date: '2024-03-13', + advisory_name: 'SUSE-15-SP4-2024-870', + advisory_type: 'security_advisory', + advisory_status: 'stable', + id: 4260, + advisory_synopsis: 'moderate: Security update for glibc', + update_date: '2024-03-13', + }, + { + date: '2024-02-28', + advisory_name: 'SUSE-15-SP4-2024-641', + advisory_type: 'bugfix', + advisory_status: 'stable', + id: 4189, + advisory_synopsis: 'Recommended update for gcc7', + update_date: '2024-02-28', + }, + ], + upgradable_packages: [ + { + from_epoch: ' ', + to_release: '150400.6.10.1', + name: 'libQt5Network5', + from_release: '150400.6.3.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 38289, + from_version: '5.15.2+kde294', + to_version: '5.15.2+kde294', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + { + from_epoch: ' ', + to_release: '150300.10.51.1', + name: 'libpython3_6m1_0', + from_release: '150300.10.40.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 36262, + from_version: '3.6.15', + to_version: '3.6.15', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + { + from_epoch: ' ', + to_release: '150400.6.10.1', + name: 'libQt5Gui5', + from_release: '150400.6.3.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 38391, + from_version: '5.15.2+kde294', + to_version: '5.15.2+kde294', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + ], + }, + }, + }; + + it('should return the correct software updates statistics', () => { + expect(getSoftwareUpdatesStats(state, hostId)).toEqual({ + numRelevantPatches: 4, + numUpgradablePackages: 3, + }); + }); + + it('should return undefined stats when there is no data for a host', () => { + const unknownHost = faker.string.uuid(); + expect(getSoftwareUpdatesStats(state, unknownHost)).toEqual({ + numRelevantPatches: undefined, + numUpgradablePackages: undefined, + }); + }); +}); From f0db7eb1702a1bb3948054bbd4d14bf8c6248e09 Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Mon, 18 Mar 2024 13:10:57 +0100 Subject: [PATCH 11/16] Connect Reducer to Store --- assets/js/state/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assets/js/state/index.js b/assets/js/state/index.js index 98234c5ca3..16d598d2c1 100644 --- a/assets/js/state/index.js +++ b/assets/js/state/index.js @@ -12,6 +12,7 @@ import catalogReducer from './catalog'; import lastExecutionsReducer from './lastExecutions'; import settingsReducer from './settings'; import userReducer from './user'; +import softwareUpdatesReducer from './softwareUpdates'; import softwareUpdatesSettingsReducer from './softwareUpdatesSettings'; import rootSaga from './sagas'; @@ -30,6 +31,7 @@ export const store = configureStore({ lastExecutions: lastExecutionsReducer, settings: settingsReducer, user: userReducer, + softwareUpdates: softwareUpdatesReducer, softwareUpdatesSettings: softwareUpdatesSettingsReducer, }, middleware: [sagaMiddleware], From 4c71dcbdd4c010279a7fdc2707119bc150ee5ede Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Mon, 18 Mar 2024 13:40:33 +0100 Subject: [PATCH 12/16] Use different transformation for `setEmptySoftwareUpdates` --- assets/js/state/selectors/softwareUpdates.test.js | 2 ++ assets/js/state/softwareUpdates.js | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/assets/js/state/selectors/softwareUpdates.test.js b/assets/js/state/selectors/softwareUpdates.test.js index edb63f30b7..d50ae0785b 100644 --- a/assets/js/state/selectors/softwareUpdates.test.js +++ b/assets/js/state/selectors/softwareUpdates.test.js @@ -4,6 +4,7 @@ import { getSoftwareUpdatesStats } from './softwareUpdates'; describe('Software Updates selector', () => { const hostId = faker.string.uuid(); const state = { + loading: false, softwareUpdates: { [hostId]: { relevant_patches: [ @@ -87,6 +88,7 @@ describe('Software Updates selector', () => { ], }, }, + errors: [], }; it('should return the correct software updates statistics', () => { diff --git a/assets/js/state/softwareUpdates.js b/assets/js/state/softwareUpdates.js index d15475c08f..54143bafab 100644 --- a/assets/js/state/softwareUpdates.js +++ b/assets/js/state/softwareUpdates.js @@ -29,11 +29,7 @@ export const softwareUpdatesSlice = createSlice({ }, setEmptySoftwareUpdates: (state, { payload: { hostId } }) => { state.loading = false; - state.softwareUpdates = Object.entries(state.softwareUpdates).reduce( - (newState, [id, updates]) => - id !== hostId ? { ...newState, [id]: updates } : newState, - {} - ); + state.softwareUpdates = { ...state.softwareUpdates, [hostId]: {} }; }, setSoftwareUpdatesErrors: (state, { payload: errors }) => { state.loading = false; From 9305795ceaac8ad87681c1220601b21d7f4c779f Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Mon, 18 Mar 2024 13:55:04 +0100 Subject: [PATCH 13/16] Clearing sets `relevant_patches` & `upgradable_packages` empty --- assets/js/state/softwareUpdates.js | 5 ++++- assets/js/state/softwareUpdates.test.js | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/assets/js/state/softwareUpdates.js b/assets/js/state/softwareUpdates.js index 54143bafab..713ae0c9c9 100644 --- a/assets/js/state/softwareUpdates.js +++ b/assets/js/state/softwareUpdates.js @@ -29,7 +29,10 @@ export const softwareUpdatesSlice = createSlice({ }, setEmptySoftwareUpdates: (state, { payload: { hostId } }) => { state.loading = false; - state.softwareUpdates = { ...state.softwareUpdates, [hostId]: {} }; + state.softwareUpdates = { + ...state.softwareUpdates, + [hostId]: { relevant_patches: [], upgradable_packages: [] }, + }; }, setSoftwareUpdatesErrors: (state, { payload: errors }) => { state.loading = false; diff --git a/assets/js/state/softwareUpdates.test.js b/assets/js/state/softwareUpdates.test.js index 81b1ce5d2b..1d51730bda 100644 --- a/assets/js/state/softwareUpdates.test.js +++ b/assets/js/state/softwareUpdates.test.js @@ -143,6 +143,7 @@ describe('SoftwareUpdates reducer', () => { loading: false, softwareUpdates: { [host1]: { relevant_patches: [], upgradable_packages: [] }, + [host2]: { relevant_patches: [], upgradable_packages: [] }, }, errors: [], }); From a75de58510671fc5bc8ad9b4df82ed47e76b0173 Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Mon, 18 Mar 2024 14:05:33 +0100 Subject: [PATCH 14/16] Clearing sets `relevant_patches` & `upgradable_packages` as `undefined` --- assets/js/state/softwareUpdates.js | 5 +---- assets/js/state/softwareUpdates.test.js | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/assets/js/state/softwareUpdates.js b/assets/js/state/softwareUpdates.js index 713ae0c9c9..54143bafab 100644 --- a/assets/js/state/softwareUpdates.js +++ b/assets/js/state/softwareUpdates.js @@ -29,10 +29,7 @@ export const softwareUpdatesSlice = createSlice({ }, setEmptySoftwareUpdates: (state, { payload: { hostId } }) => { state.loading = false; - state.softwareUpdates = { - ...state.softwareUpdates, - [hostId]: { relevant_patches: [], upgradable_packages: [] }, - }; + state.softwareUpdates = { ...state.softwareUpdates, [hostId]: {} }; }, setSoftwareUpdatesErrors: (state, { payload: errors }) => { state.loading = false; diff --git a/assets/js/state/softwareUpdates.test.js b/assets/js/state/softwareUpdates.test.js index 1d51730bda..b27fc5b33c 100644 --- a/assets/js/state/softwareUpdates.test.js +++ b/assets/js/state/softwareUpdates.test.js @@ -143,7 +143,7 @@ describe('SoftwareUpdates reducer', () => { loading: false, softwareUpdates: { [host1]: { relevant_patches: [], upgradable_packages: [] }, - [host2]: { relevant_patches: [], upgradable_packages: [] }, + [host2]: {}, }, errors: [], }); From 4a015f1e0a4b7e1b3e517a04b351fd3fdcf4121b Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Mon, 18 Mar 2024 16:47:15 +0100 Subject: [PATCH 15/16] Fix Selector --- assets/js/state/selectors/softwareUpdates.js | 2 +- .../state/selectors/softwareUpdates.test.js | 166 +++++++++--------- 2 files changed, 85 insertions(+), 83 deletions(-) diff --git a/assets/js/state/selectors/softwareUpdates.js b/assets/js/state/selectors/softwareUpdates.js index 4a2a861611..0bfa06397a 100644 --- a/assets/js/state/selectors/softwareUpdates.js +++ b/assets/js/state/selectors/softwareUpdates.js @@ -1,7 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; export const getSoftwareUpdatesForHost = (id) => (state) => - state?.softwareUpdates[id]; + state?.softwareUpdates.softwareUpdates[id]; export const getSoftwareUpdatesStats = createSelector( [(state, id) => getSoftwareUpdatesForHost(id)(state)], diff --git a/assets/js/state/selectors/softwareUpdates.test.js b/assets/js/state/selectors/softwareUpdates.test.js index d50ae0785b..7a6ddf9f7a 100644 --- a/assets/js/state/selectors/softwareUpdates.test.js +++ b/assets/js/state/selectors/softwareUpdates.test.js @@ -4,91 +4,93 @@ import { getSoftwareUpdatesStats } from './softwareUpdates'; describe('Software Updates selector', () => { const hostId = faker.string.uuid(); const state = { - loading: false, softwareUpdates: { - [hostId]: { - relevant_patches: [ - { - date: '2024-03-13', - advisory_name: 'SUSE-15-SP4-2024-877', - advisory_type: 'security_advisory', - advisory_status: 'stable', - id: 4263, - advisory_synopsis: 'important: Security update for sudo', - update_date: '2024-03-13', - }, - { - date: '2024-03-13', - advisory_name: 'SUSE-15-SP4-2024-871', - advisory_type: 'security_advisory', - advisory_status: 'stable', - id: 4261, - advisory_synopsis: 'important: Security update for vim', - update_date: '2024-03-13', - }, - { - date: '2024-03-13', - advisory_name: 'SUSE-15-SP4-2024-870', - advisory_type: 'security_advisory', - advisory_status: 'stable', - id: 4260, - advisory_synopsis: 'moderate: Security update for glibc', - update_date: '2024-03-13', - }, - { - date: '2024-02-28', - advisory_name: 'SUSE-15-SP4-2024-641', - advisory_type: 'bugfix', - advisory_status: 'stable', - id: 4189, - advisory_synopsis: 'Recommended update for gcc7', - update_date: '2024-02-28', - }, - ], - upgradable_packages: [ - { - from_epoch: ' ', - to_release: '150400.6.10.1', - name: 'libQt5Network5', - from_release: '150400.6.3.1', - to_epoch: ' ', - arch: 'x86_64', - to_package_id: 38289, - from_version: '5.15.2+kde294', - to_version: '5.15.2+kde294', - from_arch: 'x86_64', - to_arch: 'x86_64', - }, - { - from_epoch: ' ', - to_release: '150300.10.51.1', - name: 'libpython3_6m1_0', - from_release: '150300.10.40.1', - to_epoch: ' ', - arch: 'x86_64', - to_package_id: 36262, - from_version: '3.6.15', - to_version: '3.6.15', - from_arch: 'x86_64', - to_arch: 'x86_64', - }, - { - from_epoch: ' ', - to_release: '150400.6.10.1', - name: 'libQt5Gui5', - from_release: '150400.6.3.1', - to_epoch: ' ', - arch: 'x86_64', - to_package_id: 38391, - from_version: '5.15.2+kde294', - to_version: '5.15.2+kde294', - from_arch: 'x86_64', - to_arch: 'x86_64', - }, - ], + loading: false, + softwareUpdates: { + [hostId]: { + relevant_patches: [ + { + date: '2024-03-13', + advisory_name: 'SUSE-15-SP4-2024-877', + advisory_type: 'security_advisory', + advisory_status: 'stable', + id: 4263, + advisory_synopsis: 'important: Security update for sudo', + update_date: '2024-03-13', + }, + { + date: '2024-03-13', + advisory_name: 'SUSE-15-SP4-2024-871', + advisory_type: 'security_advisory', + advisory_status: 'stable', + id: 4261, + advisory_synopsis: 'important: Security update for vim', + update_date: '2024-03-13', + }, + { + date: '2024-03-13', + advisory_name: 'SUSE-15-SP4-2024-870', + advisory_type: 'security_advisory', + advisory_status: 'stable', + id: 4260, + advisory_synopsis: 'moderate: Security update for glibc', + update_date: '2024-03-13', + }, + { + date: '2024-02-28', + advisory_name: 'SUSE-15-SP4-2024-641', + advisory_type: 'bugfix', + advisory_status: 'stable', + id: 4189, + advisory_synopsis: 'Recommended update for gcc7', + update_date: '2024-02-28', + }, + ], + upgradable_packages: [ + { + from_epoch: ' ', + to_release: '150400.6.10.1', + name: 'libQt5Network5', + from_release: '150400.6.3.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 38289, + from_version: '5.15.2+kde294', + to_version: '5.15.2+kde294', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + { + from_epoch: ' ', + to_release: '150300.10.51.1', + name: 'libpython3_6m1_0', + from_release: '150300.10.40.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 36262, + from_version: '3.6.15', + to_version: '3.6.15', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + { + from_epoch: ' ', + to_release: '150400.6.10.1', + name: 'libQt5Gui5', + from_release: '150400.6.3.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 38391, + from_version: '5.15.2+kde294', + to_version: '5.15.2+kde294', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + ], + }, }, + errors: [], }, - errors: [], }; it('should return the correct software updates statistics', () => { From f156722dfe487d7aafd428b19fa04e2af6f23794 Mon Sep 17 00:00:00 2001 From: Jamie Rodriguez Date: Mon, 18 Mar 2024 16:52:18 +0100 Subject: [PATCH 16/16] Add Selector to get software updates --- assets/js/state/selectors/softwareUpdates.js | 2 + .../state/selectors/softwareUpdates.test.js | 177 +++++++++--------- 2 files changed, 95 insertions(+), 84 deletions(-) diff --git a/assets/js/state/selectors/softwareUpdates.js b/assets/js/state/selectors/softwareUpdates.js index 0bfa06397a..b17e42c258 100644 --- a/assets/js/state/selectors/softwareUpdates.js +++ b/assets/js/state/selectors/softwareUpdates.js @@ -1,5 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; +export const getSoftwareUpdates = (state) => state?.softwareUpdates; + export const getSoftwareUpdatesForHost = (id) => (state) => state?.softwareUpdates.softwareUpdates[id]; diff --git a/assets/js/state/selectors/softwareUpdates.test.js b/assets/js/state/selectors/softwareUpdates.test.js index 7a6ddf9f7a..fa8cf4f643 100644 --- a/assets/js/state/selectors/softwareUpdates.test.js +++ b/assets/js/state/selectors/softwareUpdates.test.js @@ -1,98 +1,107 @@ import { faker } from '@faker-js/faker'; -import { getSoftwareUpdatesStats } from './softwareUpdates'; +import { getSoftwareUpdates, getSoftwareUpdatesStats } from './softwareUpdates'; describe('Software Updates selector', () => { const hostId = faker.string.uuid(); + const softwareUpdates = { + [hostId]: { + relevant_patches: [ + { + date: '2024-03-13', + advisory_name: 'SUSE-15-SP4-2024-877', + advisory_type: 'security_advisory', + advisory_status: 'stable', + id: 4263, + advisory_synopsis: 'important: Security update for sudo', + update_date: '2024-03-13', + }, + { + date: '2024-03-13', + advisory_name: 'SUSE-15-SP4-2024-871', + advisory_type: 'security_advisory', + advisory_status: 'stable', + id: 4261, + advisory_synopsis: 'important: Security update for vim', + update_date: '2024-03-13', + }, + { + date: '2024-03-13', + advisory_name: 'SUSE-15-SP4-2024-870', + advisory_type: 'security_advisory', + advisory_status: 'stable', + id: 4260, + advisory_synopsis: 'moderate: Security update for glibc', + update_date: '2024-03-13', + }, + { + date: '2024-02-28', + advisory_name: 'SUSE-15-SP4-2024-641', + advisory_type: 'bugfix', + advisory_status: 'stable', + id: 4189, + advisory_synopsis: 'Recommended update for gcc7', + update_date: '2024-02-28', + }, + ], + upgradable_packages: [ + { + from_epoch: ' ', + to_release: '150400.6.10.1', + name: 'libQt5Network5', + from_release: '150400.6.3.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 38289, + from_version: '5.15.2+kde294', + to_version: '5.15.2+kde294', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + { + from_epoch: ' ', + to_release: '150300.10.51.1', + name: 'libpython3_6m1_0', + from_release: '150300.10.40.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 36262, + from_version: '3.6.15', + to_version: '3.6.15', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + { + from_epoch: ' ', + to_release: '150400.6.10.1', + name: 'libQt5Gui5', + from_release: '150400.6.3.1', + to_epoch: ' ', + arch: 'x86_64', + to_package_id: 38391, + from_version: '5.15.2+kde294', + to_version: '5.15.2+kde294', + from_arch: 'x86_64', + to_arch: 'x86_64', + }, + ], + }, + }; const state = { softwareUpdates: { loading: false, - softwareUpdates: { - [hostId]: { - relevant_patches: [ - { - date: '2024-03-13', - advisory_name: 'SUSE-15-SP4-2024-877', - advisory_type: 'security_advisory', - advisory_status: 'stable', - id: 4263, - advisory_synopsis: 'important: Security update for sudo', - update_date: '2024-03-13', - }, - { - date: '2024-03-13', - advisory_name: 'SUSE-15-SP4-2024-871', - advisory_type: 'security_advisory', - advisory_status: 'stable', - id: 4261, - advisory_synopsis: 'important: Security update for vim', - update_date: '2024-03-13', - }, - { - date: '2024-03-13', - advisory_name: 'SUSE-15-SP4-2024-870', - advisory_type: 'security_advisory', - advisory_status: 'stable', - id: 4260, - advisory_synopsis: 'moderate: Security update for glibc', - update_date: '2024-03-13', - }, - { - date: '2024-02-28', - advisory_name: 'SUSE-15-SP4-2024-641', - advisory_type: 'bugfix', - advisory_status: 'stable', - id: 4189, - advisory_synopsis: 'Recommended update for gcc7', - update_date: '2024-02-28', - }, - ], - upgradable_packages: [ - { - from_epoch: ' ', - to_release: '150400.6.10.1', - name: 'libQt5Network5', - from_release: '150400.6.3.1', - to_epoch: ' ', - arch: 'x86_64', - to_package_id: 38289, - from_version: '5.15.2+kde294', - to_version: '5.15.2+kde294', - from_arch: 'x86_64', - to_arch: 'x86_64', - }, - { - from_epoch: ' ', - to_release: '150300.10.51.1', - name: 'libpython3_6m1_0', - from_release: '150300.10.40.1', - to_epoch: ' ', - arch: 'x86_64', - to_package_id: 36262, - from_version: '3.6.15', - to_version: '3.6.15', - from_arch: 'x86_64', - to_arch: 'x86_64', - }, - { - from_epoch: ' ', - to_release: '150400.6.10.1', - name: 'libQt5Gui5', - from_release: '150400.6.3.1', - to_epoch: ' ', - arch: 'x86_64', - to_package_id: 38391, - from_version: '5.15.2+kde294', - to_version: '5.15.2+kde294', - from_arch: 'x86_64', - to_arch: 'x86_64', - }, - ], - }, - }, + softwareUpdates, errors: [], }, }; + it('should return the software updates', () => { + expect(getSoftwareUpdates(state)).toEqual({ + loading: false, + softwareUpdates, + errors: [], + }); + }); + it('should return the correct software updates statistics', () => { expect(getSoftwareUpdatesStats(state, hostId)).toEqual({ numRelevantPatches: 4,