From 8afc9cae9dfe435eec38578f7c36a88d3da527c8 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Thu, 20 Apr 2023 07:18:48 -0400 Subject: [PATCH] feat(joyride): check asset version --- src/app/AppLayout/AppLayout.tsx | 13 +++++++--- src/app/build.json | 3 ++- src/app/utils/LocalStorage.ts | 1 + src/app/utils/utils.ts | 43 +++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index e92fa125a2..dd2ac021c3 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -48,10 +48,10 @@ import { SessionState } from '@app/Shared/Services/Login.service'; import { NotificationCategory } from '@app/Shared/Services/NotificationChannel.service'; import { ServiceContext } from '@app/Shared/Services/Services'; import { FeatureLevel } from '@app/Shared/Services/Settings.service'; -import { getFromLocalStorage, saveToLocalStorage } from '@app/utils/LocalStorage'; +import { saveToLocalStorage } from '@app/utils/LocalStorage'; import { useSubscriptions } from '@app/utils/useSubscriptions'; import { useTheme } from '@app/utils/useTheme'; -import { cleanDataId, openTabForUrl, portalRoot } from '@app/utils/utils'; +import { cleanDataId, isAssetNew, openTabForUrl, portalRoot } from '@app/utils/utils'; import { Alert, AlertActionCloseButton, @@ -98,7 +98,7 @@ import { } from '@patternfly/react-icons'; import * as _ from 'lodash'; import * as React from 'react'; -import { Trans, useTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import { Link, matchPath, NavLink, useHistory, useLocation } from 'react-router-dom'; import { map } from 'rxjs/operators'; import CryostatJoyride from '../Joyride/CryostatJoyride'; @@ -587,6 +587,13 @@ const AppLayout: React.FC = ({ children }) => { [handleCloseNotificationCenter] ); + React.useEffect(() => { + if (showUserIcon && isAssetNew(build.version)) { + handleOpenGuidedTour(); + saveToLocalStorage('ASSET_VERSION', build.version); + } + }, [handleOpenGuidedTour, showUserIcon]); + return ( diff --git a/src/app/build.json b/src/app/build.json index 4750f2f4c5..a4c1949608 100644 --- a/src/app/build.json +++ b/src/app/build.json @@ -11,5 +11,6 @@ "fileIssueUrl": "https://github.com/cryostatio/cryostat/issues/new?labels=user+report,bug&body=Affects+__REPLACE_VERSION__", "mailingListName": "Google Groups", "mailingListUrl": "https://groups.google.com/g/cryostat-development", - "licenseUrl": "https://github.com/cryostatio/cryostat/blob/main/LICENSE" + "licenseUrl": "https://github.com/cryostatio/cryostat/blob/main/LICENSE", + "version": "2.4.0-dev" } diff --git a/src/app/utils/LocalStorage.ts b/src/app/utils/LocalStorage.ts index 230e153308..31ed9062be 100644 --- a/src/app/utils/LocalStorage.ts +++ b/src/app/utils/LocalStorage.ts @@ -37,6 +37,7 @@ */ /* eslint-disable @typescript-eslint/no-explicit-any */ export enum LocalStorageKey { + ASSET_VERSION, FEATURE_LEVEL, DASHBOARD_CFG, AUTOMATED_ANALYSIS_FILTERS, diff --git a/src/app/utils/utils.ts b/src/app/utils/utils.ts index f67af912fa..33376507af 100644 --- a/src/app/utils/utils.ts +++ b/src/app/utils/utils.ts @@ -40,6 +40,7 @@ import { ISortBy, SortByDirection } from '@patternfly/react-table'; import _ from 'lodash'; import { useHistory } from 'react-router-dom'; import { BehaviorSubject, Observable } from 'rxjs'; +import { getFromLocalStorage } from './LocalStorage'; const SECOND_MILLIS = 1000; const MINUTE_MILLIS = 60 * SECOND_MILLIS; @@ -266,3 +267,45 @@ export const getActiveTab = (search: string, key: string, supportedTabs: T[], }; export const clickOutside = () => document.body.click(); + +export interface SemVer { + major: number; + minor: number; + patch: number; +} + +// https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string +export const semverRegex = + /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; + +export const getSemVer = (str: string): SemVer | undefined => { + const matched = str.match(semverRegex); + if (matched) { + const [_, major, minor, patch] = matched; + return { + major: Number(major), + minor: Number(minor), + patch: Number(patch), + }; + } + return undefined; +}; + +const convert = (ver: SemVer) => ver.major * 100 + ver.minor * 10 + ver.patch; + +export const compareSemVer = (ver1: SemVer, ver2: SemVer): number => { + const _ver1 = convert(ver1); + const _ver2 = convert(ver2); + return _ver1 > _ver2 ? 1 : _ver1 < _ver2 ? -1 : 0; +}; + +export const isAssetNew = (currVerStr: string) => { + const oldVer = getSemVer(getFromLocalStorage('ASSET_VERSION', '0.0.0')); + const currVer = getSemVer(currVerStr); + + if (!currVer) { + throw new Error(`Invalid asset version: ${currVer}`); + } + // Invalid (old) version is ignored. + return !oldVer || compareSemVer(currVer, oldVer) > 0; +};