Skip to content

Commit

Permalink
feat(app): check for updates on startup
Browse files Browse the repository at this point in the history
  • Loading branch information
jamaljsr committed Aug 3, 2022
1 parent d3e98f4 commit e864a6b
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 5 deletions.
9 changes: 9 additions & 0 deletions src/components/common/ImageUpdatesModal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ describe('ImageUpdatesModal', () => {
expect(await findByText('test-error')).toBeInTheDocument();
});

it('should toggle check for updates on startup', async () => {
const { findByText, getByText, store } = await renderComponent();
expect(await findByText('You are up to date!')).toBeInTheDocument();
fireEvent.click(getByText('Automatically check for updates on startup'));
expect(store.getState().app.settings.checkForUpdatesOnStartup).toBe(true);
fireEvent.click(getByText('Automatically check for updates on startup'));
expect(store.getState().app.settings.checkForUpdatesOnStartup).toBe(false);
});

describe('with available updates', () => {
beforeEach(() => {
mockRepoService.checkForUpdates.mockResolvedValue({
Expand Down
24 changes: 20 additions & 4 deletions src/components/common/ImageUpdatesModal.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { ReactNode, useState } from 'react';
import React, { ReactNode, useCallback, useState } from 'react';
import { useAsync, useAsyncCallback } from 'react-async-hook';
import styled from '@emotion/styled';
import { Modal, Result } from 'antd';
import { Checkbox, Modal, Result } from 'antd';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
import { usePrefixedTranslation } from 'hooks';
import { NodeImplementation } from 'shared/types';
import { useStoreActions } from 'store';
import { useStoreActions, useStoreState } from 'store';
import { DockerRepoUpdates } from 'types';
import { dockerConfigs } from 'utils/constants';
import { DetailsList, Loader } from 'components/common';
Expand Down Expand Up @@ -42,7 +43,10 @@ const ImageUpdatesModal: React.FC<Props> = ({ onClose }) => {
const { l } = usePrefixedTranslation('cmps.designer.default.ImageUpdatesModal');

const [repoUpdates, setRepoUpdates] = useState<DockerRepoUpdates>();
const { notify, checkForRepoUpdates, saveRepoState } = useStoreActions(s => s.app);
const { settings } = useStoreState(s => s.app);
const { notify, checkForRepoUpdates, saveRepoState, updateSettings } = useStoreActions(
s => s.app,
);

const checkAsync = useAsync(async () => {
const res = await checkForRepoUpdates();
Expand All @@ -51,6 +55,10 @@ const ImageUpdatesModal: React.FC<Props> = ({ onClose }) => {
}
}, []);

const handleUpdatesToggled = useCallback((e: CheckboxChangeEvent) => {
updateSettings({ checkForUpdatesOnStartup: e.target.checked });
}, []);

const updateAsync = useAsyncCallback(async () => {
try {
if (repoUpdates) {
Expand Down Expand Up @@ -111,6 +119,14 @@ const ImageUpdatesModal: React.FC<Props> = ({ onClose }) => {
onOk={updateAsync.execute}
>
{cmp}
<Styled.Details>
<Checkbox
onChange={handleUpdatesToggled}
checked={settings.checkForUpdatesOnStartup}
>
{l('checkForUpdates')}
</Checkbox>
</Styled.Details>
</Modal>
);
};
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"cmps.designer.default.ImageUpdatesModal.addError": "Failed to add the new versions",
"cmps.designer.default.ImageUpdatesModal.success": "The new Docker Images can now be used",
"cmps.designer.default.ImageUpdatesModal.error": "Unable to update",
"cmps.designer.default.ImageUpdatesModal.checkForUpdates": "Automatically check for updates on startup",
"cmps.designer.link.Backend.title": "Chain Backend Connection",
"cmps.designer.link.Backend.desc": "Lightning nodes rely on blockchain nodes to send transactions and receive confirmation notifications about transactions they care about. You can change this connection if you'd like to.",
"cmps.designer.link.Backend.lightningTitle": "Lightning Node",
Expand Down
48 changes: 48 additions & 0 deletions src/store/models/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defaultRepoState } from 'utils/constants';
import { injections } from 'utils/tests';
import appModel from './app';
import designerModel from './designer';
import modalsModel from './modals';
import networkModel from './network';

const mockDockerService = injections.dockerService as jest.Mocked<
Expand All @@ -21,6 +22,7 @@ describe('App model', () => {
app: appModel,
network: networkModel,
designer: designerModel,
modals: modalsModel,
};
// initialize store for type inference
let store = createStore(rootModel, { injections });
Expand All @@ -37,6 +39,7 @@ describe('App model', () => {
mockSettingsService.load.mockResolvedValue({
lang: 'en-US',
showAllNodeVersions: true,
checkForUpdatesOnStartup: false,
theme: 'dark',
nodeImages: { custom: [], managed: [] },
});
Expand Down Expand Up @@ -72,6 +75,51 @@ describe('App model', () => {
expect(store.getState().app.settings.showAllNodeVersions).toBe(true);
});

describe('check for updates', () => {
beforeEach(() => {
mockSettingsService.load.mockResolvedValue({
lang: 'en-US',
showAllNodeVersions: true,
checkForUpdatesOnStartup: true,
theme: 'dark',
nodeImages: { custom: [], managed: [] },
});
});

it('should check for updates on startup', async () => {
mockRepoService.checkForUpdates.mockResolvedValue({
state: defaultRepoState,
});
await store.getActions().app.initialize();
expect(store.getState().app.initialized).toBe(true);
expect(mockRepoService.checkForUpdates).toBeCalledTimes(1);
expect(store.getState().modals.imageUpdates.visible).toBe(false);
});

it('should display updates modal on startup', async () => {
mockRepoService.checkForUpdates.mockResolvedValue({
state: defaultRepoState,
updates: {
LND: ['0.99.0-beta'], // a new version available for LND
'c-lightning': [],
eclair: [],
bitcoind: [],
btcd: [],
},
});

await store.getActions().app.initialize();
expect(store.getState().app.initialized).toBe(true);
expect(mockRepoService.checkForUpdates).toBeCalledTimes(1);
expect(store.getState().modals.imageUpdates.visible).toBe(true);
});

it('should not throw an error', async () => {
mockRepoService.checkForUpdates.mockRejectedValue(new Error('something'));
expect(store.getActions().app.initialize()).resolves.not.toThrow();
});
});

describe('with mocked actions', () => {
beforeEach(() => {
// reset the store before each test run
Expand Down
18 changes: 17 additions & 1 deletion src/store/models/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface AppModel {
setRepoState: Action<AppModel, DockerRepoState>;
loadRepoState: Thunk<AppModel, void, StoreInjections, RootModel>;
saveRepoState: Thunk<AppModel, DockerRepoState, StoreInjections, RootModel>;
queryRepoUpdates: Thunk<AppModel, void, StoreInjections, RootModel>;
checkForRepoUpdates: Thunk<
AppModel,
void,
Expand All @@ -73,6 +74,7 @@ const appModel: AppModel = {
lang: getI18n().language,
theme: 'dark',
showAllNodeVersions: false,
checkForUpdatesOnStartup: false,
nodeImages: {
managed: [],
custom: [],
Expand Down Expand Up @@ -104,12 +106,15 @@ const appModel: AppModel = {
setInitialized: action((state, initialized) => {
state.initialized = initialized;
}),
initialize: thunk(async (actions, _, { getStoreActions }) => {
initialize: thunk(async (actions, _, { getStoreActions, getState }) => {
await actions.loadSettings();
await actions.loadRepoState();
await getStoreActions().network.load();
await actions.getDockerVersions({});
await actions.getDockerImages();
if (getState().settings.checkForUpdatesOnStartup) {
await actions.queryRepoUpdates();
}
actions.setInitialized(true);
}),
setSettings: action((state, settings) => {
Expand Down Expand Up @@ -200,6 +205,17 @@ const appModel: AppModel = {
await injections.repoService.save(repoState);
actions.setRepoState(repoState);
}),
queryRepoUpdates: thunk(async (actions, payload, { getStoreActions }) => {
try {
const res = await actions.checkForRepoUpdates();
if (res.updates) {
getStoreActions().modals.showImageUpdates();
}
} catch (error) {
// just log errors and don't display them in the UI
warn('Failed to check for image updates', error);
}
}),
checkForRepoUpdates: thunk(async (actions, payload, { injections, getState }) => {
const { dockerRepoState } = getState();
return injections.repoService.checkForUpdates(dockerRepoState);
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface AppSettings {
lang: string;
theme: 'light' | 'dark';
showAllNodeVersions: boolean;
checkForUpdatesOnStartup: boolean;
/** lists of docker image customizations */
nodeImages: {
managed: ManagedImage[];
Expand Down

0 comments on commit e864a6b

Please sign in to comment.