From 3ed209a34db24e33251b6e1adee8d57055f365a6 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Tue, 3 Dec 2024 15:55:04 -0500 Subject: [PATCH] Fix server update message not handling mutli-digit version numbers (#29436) --- .../dashboard_checks/updates.test.ts | 113 ++++++++++++++++++ .../dashboard_checks/updates.ts | 98 +++++++-------- 2 files changed, 162 insertions(+), 49 deletions(-) create mode 100644 webapp/channels/src/components/admin_console/workspace-optimization/dashboard_checks/updates.test.ts diff --git a/webapp/channels/src/components/admin_console/workspace-optimization/dashboard_checks/updates.test.ts b/webapp/channels/src/components/admin_console/workspace-optimization/dashboard_checks/updates.test.ts new file mode 100644 index 0000000000..b2125a5d7f --- /dev/null +++ b/webapp/channels/src/components/admin_console/workspace-optimization/dashboard_checks/updates.test.ts @@ -0,0 +1,113 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {fetchAndCompareVersion} from './updates'; + +import {ItemStatus} from '../dashboard.type'; + +jest.mock('mattermost-redux/client', () => ({ + Client4: { + getBaseRoute: jest.fn(() => 'http://localhost/api/v4'), + }, +})); + +describe('fetchAndCompareVersion', () => { + beforeEach(() => { + global.fetch = jest.fn(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should return major update available', async () => { + (global.fetch as jest.Mock).mockResolvedValue({ + json: jest.fn().mockResolvedValue({tag_name: 'v9.1.2', body: 'New major version available'}), + }); + + const formatMessage = jest.fn(({defaultMessage}) => defaultMessage); + const result = await fetchAndCompareVersion('5.6.0', formatMessage); + + expect(result.type).toBe('Major'); + expect(result.description).toBe('New major version available'); + expect(result.status).toBe(ItemStatus.ERROR); + }); + + it('should return major update available - single to double digit', async () => { + (global.fetch as jest.Mock).mockResolvedValue({ + json: jest.fn().mockResolvedValue({tag_name: 'v10.0.0', body: 'New major version available'}), + }); + + const formatMessage = jest.fn(({defaultMessage}) => defaultMessage); + const result = await fetchAndCompareVersion('9.1.0', formatMessage); + + expect(result.type).toBe('Major'); + expect(result.description).toBe('New major version available'); + expect(result.status).toBe(ItemStatus.ERROR); + }); + + it('should return minor update available', async () => { + (global.fetch as jest.Mock).mockResolvedValue({ + json: jest.fn().mockResolvedValue({tag_name: 'v9.2.0', body: 'New minor version available'}), + }); + + const formatMessage = jest.fn(({defaultMessage}) => defaultMessage); + const result = await fetchAndCompareVersion('9.1.0', formatMessage); + + expect(result.type).toBe('Minor'); + expect(result.description).toBe('New minor version available'); + expect(result.status).toBe(ItemStatus.WARNING); + }); + + it('should return minor update available - single to double digit', async () => { + (global.fetch as jest.Mock).mockResolvedValue({ + json: jest.fn().mockResolvedValue({tag_name: 'v9.11.0', body: 'New minor version available'}), + }); + + const formatMessage = jest.fn(({defaultMessage}) => defaultMessage); + const result = await fetchAndCompareVersion('9.5.0', formatMessage); + + expect(result.type).toBe('Minor'); + expect(result.description).toBe('New minor version available'); + expect(result.status).toBe(ItemStatus.WARNING); + }); + + it('should return patch update available', async () => { + (global.fetch as jest.Mock).mockResolvedValue({ + json: jest.fn().mockResolvedValue({tag_name: 'v9.1.1', body: 'New patch version available'}), + }); + + const formatMessage = jest.fn(({defaultMessage}) => defaultMessage); + const result = await fetchAndCompareVersion('9.1.0', formatMessage); + + expect(result.type).toBe('Patch'); + expect(result.description).toBe('New patch version available'); + expect(result.status).toBe(ItemStatus.INFO); + }); + + it('should return patch update available - single to double digit', async () => { + (global.fetch as jest.Mock).mockResolvedValue({ + json: jest.fn().mockResolvedValue({tag_name: 'v9.1.11', body: 'New patch version available'}), + }); + + const formatMessage = jest.fn(({defaultMessage}) => defaultMessage); + const result = await fetchAndCompareVersion('9.1.4', formatMessage); + + expect(result.type).toBe('Patch'); + expect(result.description).toBe('New patch version available'); + expect(result.status).toBe(ItemStatus.INFO); + }); + + it('should return no update available', async () => { + (global.fetch as jest.Mock).mockResolvedValue({ + json: jest.fn().mockResolvedValue({tag_name: 'v9.1.0', body: 'No new version available'}), + }); + + const formatMessage = jest.fn(({defaultMessage}) => defaultMessage); + const result = await fetchAndCompareVersion('9.1.0', formatMessage); + + expect(result.type).toBe(''); + expect(result.description).toBe('No new version available'); + expect(result.status).toBe(ItemStatus.OK); + }); +}); diff --git a/webapp/channels/src/components/admin_console/workspace-optimization/dashboard_checks/updates.ts b/webapp/channels/src/components/admin_console/workspace-optimization/dashboard_checks/updates.ts index 3a0d4f6a8f..3ddad5854d 100644 --- a/webapp/channels/src/components/admin_console/workspace-optimization/dashboard_checks/updates.ts +++ b/webapp/channels/src/components/admin_console/workspace-optimization/dashboard_checks/updates.ts @@ -18,55 +18,7 @@ const testServerVersion = async ( formatMessage: ReturnType['formatMessage'], options: Options, ) => { - const fetchVersion = async ( - installedVersion: string, - formatMessage: ReturnType['formatMessage'], - ) => { - const result = await fetch(`${Client4.getBaseRoute()}/latest_version`).then((result) => result.json()); - - if (result.tag_name) { - const sanitizedVersion = result.tag_name.startsWith('v') ? result.tag_name.slice(1) : result.tag_name; - const newVersionParts = sanitizedVersion.split('.'); - const installedVersionParts = installedVersion.split('.').slice(0, 3); - - // quick general check if a newer version is available - let type = ''; - let status: ItemStatus = ItemStatus.OK; - - if (newVersionParts.join('') > installedVersionParts.join('')) { - // get correct values to be inserted into the accordion item - switch (true) { - case newVersionParts[0] > installedVersionParts[0]: - type = formatMessage({ - id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.major', - defaultMessage: 'Major', - }); - status = ItemStatus.ERROR; - break; - case newVersionParts[1] > installedVersionParts[1]: - type = formatMessage({ - id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.minor', - defaultMessage: 'Minor', - }); - status = ItemStatus.WARNING; - break; - case newVersionParts[2] > installedVersionParts[2]: - type = formatMessage({ - id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.patch', - defaultMessage: 'Patch', - }); - status = ItemStatus.INFO; - break; - } - } - - return {type, description: result.body, status}; - } - - return {type: '', description: '', status: ItemStatus.OK}; - }; - - const serverVersion = await fetchVersion(options.installedVersion, formatMessage); + const serverVersion = await fetchAndCompareVersion(options.installedVersion, formatMessage); return { id: 'server_version', title: formatMessage({ @@ -85,6 +37,54 @@ const testServerVersion = async ( }; }; +export const fetchAndCompareVersion = async ( + installedVersion: string, + formatMessage: ReturnType['formatMessage'], +) => { + const result = await fetch(`${Client4.getBaseRoute()}/latest_version`).then((result) => result.json()); + + if (result.tag_name) { + const sanitizedVersion = result.tag_name.startsWith('v') ? result.tag_name.slice(1) : result.tag_name; + const newVersionParts = sanitizedVersion.split('.'); + const installedVersionParts = installedVersion.split('.').slice(0, 3); + + // quick general check if a newer version is available + let type = ''; + let status: ItemStatus = ItemStatus.OK; + + if (sanitizedVersion.localeCompare(installedVersion, undefined, {numeric: true, sensitivity: 'base'}) > 0) { + // get correct values to be inserted into the accordion item + switch (true) { + case Number(newVersionParts[0]) > Number(installedVersionParts[0]): + type = formatMessage({ + id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.major', + defaultMessage: 'Major', + }); + status = ItemStatus.ERROR; + break; + case Number(newVersionParts[1]) > Number(installedVersionParts[1]): + type = formatMessage({ + id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.minor', + defaultMessage: 'Minor', + }); + status = ItemStatus.WARNING; + break; + case Number(newVersionParts[2]) > Number(installedVersionParts[2]): + type = formatMessage({ + id: 'admin.reporting.workspace_optimization.updates.server_version.update_type.patch', + defaultMessage: 'Patch', + }); + status = ItemStatus.INFO; + break; + } + } + + return {type, description: result.body, status}; + } + + return {type: '', description: '', status: ItemStatus.OK}; +}; + export const runUpdateChecks = async ( config: Partial, formatMessage: ReturnType['formatMessage'],