refactor: remove the ability to update tipi through the ui

This commit is contained in:
Nicolas Meienberger 2023-09-02 19:21:18 +02:00
parent 186951cd69
commit 2c0126db3f
12 changed files with 3 additions and 226 deletions

View file

@ -52,29 +52,10 @@ describe('Test: StatusProvider', () => {
unmount();
});
it('should render StatusScreen when system is UPDATING', async () => {
const { result, unmount } = renderHook(() => useSystemStore());
act(() => {
result.current.setStatus('UPDATING');
});
render(
<StatusProvider>
<div>system running</div>
</StatusProvider>,
);
await waitFor(() => {
expect(screen.getByText('Your system is updating...')).toBeInTheDocument();
});
unmount();
});
it('should reload the page when system is RUNNING after being something else than RUNNING', async () => {
const { result, unmount } = renderHook(() => useSystemStore());
act(() => {
result.current.setStatus('UPDATING');
result.current.setStatus('RESTARTING');
});
render(
@ -84,7 +65,7 @@ describe('Test: StatusProvider', () => {
);
await waitFor(() => {
expect(screen.getByText('Your system is updating...')).toBeInTheDocument();
expect(screen.getByText('Your system is restarting...')).toBeInTheDocument();
});
act(() => {

View file

@ -23,9 +23,6 @@ export const StatusProvider: React.FC<IProps> = ({ children }) => {
if (status === 'RESTARTING') {
s.current = 'RESTARTING';
}
if (status === 'UPDATING') {
s.current = 'UPDATING';
}
}, [status, s, setPollStatus]);
if (s.current === 'LOADING') {
@ -36,9 +33,5 @@ export const StatusProvider: React.FC<IProps> = ({ children }) => {
return <StatusScreen title="Your system is restarting..." subtitle="Please do not refresh this page" />;
}
if (s.current === 'UPDATING') {
return <StatusScreen title="Your system is updating..." subtitle="Please do not refresh this page" />;
}
return children;
};

View file

@ -33,7 +33,7 @@
"domain-already-in-use": "Domain {domain} is already in use by app {id}",
"could-not-get-latest-version": "Could not get latest version",
"current-version-is-latest": "Current version is already up to date",
"major-version-update": "The major version has changed. Please update manually (instructions on GitHub)",
"major-version-update": "The major version has changed. Please update manually (instructions in release notes)",
"demo-mode-limit": "Only 6 apps can be installed in the demo mode. Please uninstall an other app to install a new one."
},
"success": {}
@ -229,7 +229,6 @@
"maintenance-title": "Maintenance",
"maintenance-subtitle": "Common actions to perform on your instance",
"restart": "Restart",
"update": "Update to {version}",
"already-latest": "Already up to date"
},
"settings": {

View file

@ -9,12 +9,6 @@ export const handlers = [
type: 'query',
response: { current: '1.0.0', latest: '1.0.0', body: 'hello' },
}),
getTRPCMock({
path: ['system', 'update'],
type: 'mutation',
response: true,
delay: 100,
}),
getTRPCMock({
path: ['system', 'restart'],
type: 'mutation',

View file

@ -1,28 +0,0 @@
import React from 'react';
import { Button } from '../../../../components/ui/Button';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from '../../../../components/ui/Dialog';
interface IProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
loading: boolean;
}
export const UpdateModal: React.FC<IProps> = ({ isOpen, onClose, onConfirm, loading }) => (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent size="sm">
<DialogHeader>
<h5 className="modal-title">Update Tipi</h5>
</DialogHeader>
<DialogDescription>
<div className="text-muted">Would you like to update Tipi to the latest version?</div>
</DialogDescription>
<DialogFooter>
<Button onClick={onConfirm} className="btn-success" loading={loading}>
Update
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);

View file

@ -1 +0,0 @@
export { UpdateModal } from './UpdateModal';

View file

@ -14,58 +14,6 @@ describe('Test: GeneralActions', () => {
expect(screen.getByText('Actions')).toBeInTheDocument();
});
it('should show toast if update mutation fails', async () => {
// arrange
server.use(getTRPCMock({ path: ['system', 'getVersion'], response: { current: '1.0.0', latest: '2.0.0', body: '' } }));
server.use(getTRPCMockError({ path: ['system', 'update'], type: 'mutation', status: 500, message: 'Something went wrong' }));
render(<GeneralActions />);
await waitFor(() => {
expect(screen.getByText('Update to 2.0.0')).toBeInTheDocument();
});
const updateButton = screen.getByRole('button', { name: /Update/i });
fireEvent.click(updateButton);
// act
const updateButtonModal = screen.getByRole('button', { name: /Update/i });
fireEvent.click(updateButtonModal);
// assert
await waitFor(() => {
expect(screen.getByText(/Something went wrong/)).toBeInTheDocument();
});
});
it('should set poll status to true if update mutation succeeds', async () => {
// arrange
server.use(getTRPCMock({ path: ['system', 'getVersion'], response: { current: '1.0.0', latest: '2.0.0', body: '' } }));
server.use(getTRPCMock({ path: ['system', 'update'], type: 'mutation', response: true }));
const { result } = renderHook(() => useSystemStore());
result.current.setStatus('RUNNING');
render(
<StatusProvider>
<GeneralActions />
</StatusProvider>,
);
await waitFor(() => {
expect(screen.getByText('Update to 2.0.0')).toBeInTheDocument();
});
const updateButton = screen.getByRole('button', { name: /Update/i });
fireEvent.click(updateButton);
// act
const updateButtonModal = screen.getByRole('button', { name: /Update/i });
fireEvent.click(updateButtonModal);
result.current.setStatus('UPDATING');
// assert
await waitFor(() => {
expect(screen.getByText('Your system is updating...')).toBeInTheDocument();
});
expect(result.current.pollStatus).toBe(true);
});
it('should show toast if restart mutation fails', async () => {
// arrange
server.use(getTRPCMockError({ path: ['system', 'restart'], type: 'mutation', status: 500, message: 'Something went badly' }));

View file

@ -8,7 +8,6 @@ import { MessageKey } from '@/server/utils/errors';
import { Button } from '../../../../components/ui/Button';
import { useDisclosure } from '../../../../hooks/useDisclosure';
import { RestartModal } from '../../components/RestartModal';
import { UpdateModal } from '../../components/UpdateModal/UpdateModal';
import { trpc } from '../../../../utils/trpc';
import { useSystemStore } from '../../../../state/systemStore';
@ -19,25 +18,10 @@ export const GeneralActions = () => {
const [loading, setLoading] = React.useState(false);
const { setPollStatus } = useSystemStore();
const restartDisclosure = useDisclosure();
const updateDisclosure = useDisclosure();
const defaultVersion = '0.0.0';
const isLatest = semver.gte(versionQuery.data?.current || defaultVersion, versionQuery.data?.latest || defaultVersion);
const update = trpc.system.update.useMutation({
onMutate: () => {
setLoading(true);
},
onSuccess: async () => {
setPollStatus(true);
},
onError: (e) => {
updateDisclosure.close();
setLoading(false);
toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables }));
},
});
const restart = trpc.system.restart.useMutation({
onMutate: () => {
setLoading(true);
@ -71,9 +55,6 @@ export const GeneralActions = () => {
</div>
</div>
)}
<Button onClick={updateDisclosure.open} className="mt-3 mr-2 btn-success">
{t('settings.actions.update', { version: versionQuery.data?.latest })}
</Button>
</div>
);
};
@ -92,7 +73,6 @@ export const GeneralActions = () => {
</div>
</div>
<RestartModal isOpen={restartDisclosure.isOpen} onClose={restartDisclosure.close} onConfirm={() => restart.mutate()} loading={loading} />
<UpdateModal isOpen={updateDisclosure.isOpen} onClose={updateDisclosure.close} onConfirm={() => update.mutate()} loading={loading} />
</>
);
};

View file

@ -3,7 +3,6 @@ import { create } from 'zustand';
const SYSTEM_STATUS = {
RUNNING: 'RUNNING',
RESTARTING: 'RESTARTING',
UPDATING: 'UPDATING',
LOADING: 'LOADING',
} as const;
export type SystemStatus = (typeof SYSTEM_STATUS)[keyof typeof SYSTEM_STATUS];

View file

@ -12,7 +12,6 @@ export const systemRouter = router({
systemInfo: protectedProcedure.query(SystemServiceClass.systemInfo),
getVersion: publicProcedure.query(SystemService.getVersion),
restart: protectedProcedure.mutation(SystemService.restart),
update: protectedProcedure.mutation(SystemService.update),
updateSettings: protectedProcedure.input(settingsSchema).mutation(({ input }) => TipiConfig.setSettings(input)),
getSettings: protectedProcedure.query(TipiConfig.getSettings),
});

View file

@ -160,59 +160,3 @@ describe('Test: restart', () => {
await expect(SystemService.restart()).rejects.toThrow('server-messages.errors.not-allowed-in-demo');
});
});
describe('Test: update', () => {
it('Should return true', async () => {
// Arrange
EventDispatcher.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
setConfig('version', '0.0.1');
await cache.set('latestVersion', '0.0.2');
// Act
const update = await SystemService.update();
// Assert
expect(update).toBeTruthy();
});
it('Should throw an error if latest version is not set', async () => {
// Arrange
await cache.del('latestVersion');
server.use(
rest.get('https://api.github.com/repos/meienberger/runtipi/releases/latest', (_, res, ctx) => {
return res(ctx.json({ name: null }));
}),
);
setConfig('version', '0.0.1');
// Act & Assert
await expect(SystemService.update()).rejects.toThrow('server-messages.errors.could-not-get-latest-version');
});
it('Should throw if current version is higher than latest', async () => {
// Arrange
setConfig('version', '0.0.2');
await cache.set('latestVersion', '0.0.1');
// Act & Assert
await expect(SystemService.update()).rejects.toThrow('server-messages.errors.current-version-is-latest');
});
it('Should throw if current version is equal to latest', async () => {
// Arrange
setConfig('version', '0.0.1');
await cache.set('latestVersion', '0.0.1');
// Act & Assert
await expect(SystemService.update()).rejects.toThrow('server-messages.errors.current-version-is-latest');
});
it('Should throw an error if there is a major version difference', async () => {
// Arrange
setConfig('version', '0.0.1');
await cache.set('latestVersion', '1.0.0');
// Act & Assert
await expect(SystemService.update()).rejects.toThrow('server-messages.errors.major-version-update');
});
});

View file

@ -1,4 +1,3 @@
import semver from 'semver';
import { z } from 'zod';
import axios from 'redaxios';
import { TranslatedError } from '@/server/utils/errors';
@ -84,36 +83,6 @@ export class SystemServiceClass {
return info.data;
};
public update = async (): Promise<boolean> => {
const { current, latest } = await this.getVersion();
if (TipiConfig.getConfig().NODE_ENV === 'development') {
throw new TranslatedError('server-messages.errors.not-allowed-in-dev');
}
if (!latest) {
throw new TranslatedError('server-messages.errors.could-not-get-latest-version');
}
if (semver.gt(current, latest)) {
throw new TranslatedError('server-messages.errors.current-version-is-latest');
}
if (semver.eq(current, latest)) {
throw new TranslatedError('server-messages.errors.current-version-is-latest');
}
if (semver.major(current) !== semver.major(latest)) {
throw new TranslatedError('server-messages.errors.major-version-update');
}
TipiConfig.setConfig('status', 'UPDATING');
this.dispatcher.dispatchEvent({ type: 'system', command: 'update', version: latest });
return true;
};
public restart = async (): Promise<boolean> => {
if (TipiConfig.getConfig().NODE_ENV === 'development') {
throw new TranslatedError('server-messages.errors.not-allowed-in-dev');