From 2a348e916caf2c213943c15b2b14f1548a234617 Mon Sep 17 00:00:00 2001 From: palash-signoz Date: Tue, 5 Apr 2022 18:21:25 +0530 Subject: [PATCH] feat: version page is added (#924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat👔 : getLatestVersion api is added * chore: VERSION page is added * feat: ✨ version page is added * chore: all string is grabbed from locale * chore: warning is removed * chore: translation json is added * chore: feedback about version is added * chore: made two different functions * unused import is removed * feat: version changes are updated * chore: if current version is present then it is displayed --- frontend/public/locales/en/translation.json | 9 ++ frontend/src/AppRoutes/pageComponents.ts | 4 + frontend/src/AppRoutes/routes.ts | 6 + frontend/src/api/user/getLatestVersion.ts | 25 ++++ frontend/src/constants/routes.ts | 1 + frontend/src/container/AppLayout/index.tsx | 92 ++++++++++++++- .../container/Header/Breadcrumbs/index.tsx | 1 + frontend/src/container/SideNav/index.tsx | 66 +++++++++-- frontend/src/container/SideNav/styles.ts | 26 ++++- frontend/src/container/Version/index.tsx | 110 ++++++++++++++++++ frontend/src/container/Version/styles.ts | 8 ++ frontend/src/pages/Status/index.tsx | 8 ++ frontend/src/store/reducers/app.ts | 30 +++++ frontend/src/types/actions/app.ts | 37 +++++- .../src/types/api/user/getLatestVersion.ts | 18 +++ frontend/src/types/reducer/app.ts | 4 + 16 files changed, 433 insertions(+), 12 deletions(-) create mode 100644 frontend/src/api/user/getLatestVersion.ts create mode 100644 frontend/src/container/Version/index.tsx create mode 100644 frontend/src/container/Version/styles.ts create mode 100644 frontend/src/pages/Status/index.tsx create mode 100644 frontend/src/types/api/user/getLatestVersion.ts diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 571ac5e43a..23330080e6 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -1,5 +1,14 @@ { "monitor_signup": "Monitor your applications. Find what is causing issues.", + "version": "Version", + "latest_version": "Latest version", + "current_version": "Current version", + "release_notes": "Release Notes", + "read_how_to_upgrade": "Read instructions on how to upgrade", + "latest_version_signoz": "You are running the latest version of SigNoz.", + "stale_version": "You are on an older version and may be loosing on the latest features we have shipped. We recommend to upgrade to the latest version", + "oops_something_went_wrong_version": "Oops.. facing issues with fetching updated version information", + "n_a": "N/A", "routes": { "general": "General", "alert_channels": "Alert Channels" diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index eb7430eb3c..25d4dc5886 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -85,3 +85,7 @@ export const EditAlertChannelsAlerts = Loadable( export const AllAlertChannels = Loadable( () => import(/* webpackChunkName: "All Channels" */ 'pages/AllAlertChannels'), ); + +export const StatusPage = Loadable( + () => import(/* webpackChunkName: "All Status" */ 'pages/Status'), +); diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index 474f71f510..10f5a1997a 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -17,6 +17,7 @@ import { ServicesTablePage, SettingsPage, SignupPage, + StatusPage, TraceDetail, TraceFilter, UsageExplorerPage, @@ -113,6 +114,11 @@ const routes: AppRoutes[] = [ exact: true, component: AllAlertChannels, }, + { + path: ROUTES.VERSION, + exact: true, + component: StatusPage, + }, ]; interface AppRoutes { diff --git a/frontend/src/api/user/getLatestVersion.ts b/frontend/src/api/user/getLatestVersion.ts new file mode 100644 index 0000000000..28a72f78be --- /dev/null +++ b/frontend/src/api/user/getLatestVersion.ts @@ -0,0 +1,25 @@ +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import axios, { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps } from 'types/api/user/getLatestVersion'; + +const getLatestVersion = async (): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response = await axios.get( + `https://api.github.com/repos/signoz/signoz/releases/latest`, + ); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getLatestVersion; diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index acd95003ea..e62cec59f2 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -17,6 +17,7 @@ const ROUTES = { ALL_CHANNELS: '/settings/channels', CHANNELS_NEW: '/setting/channels/new', CHANNELS_EDIT: '/setting/channels/edit/:id', + VERSION: '/status', }; export default ROUTES; diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 5230ca5bae..1fa47522c5 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -1,11 +1,24 @@ +import { notification } from 'antd'; +import getLatestVersion from 'api/user/getLatestVersion'; +import getVersion from 'api/user/getVersion'; import ROUTES from 'constants/routes'; import TopNav from 'container/Header'; import SideNav from 'container/SideNav'; +import useFetch from 'hooks/useFetch'; import history from 'lib/history'; -import React, { ReactNode, useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; +import React, { ReactNode, useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; +import { Dispatch } from 'redux'; import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { + UPDATE_CURRENT_ERROR, + UPDATE_CURRENT_VERSION, + UPDATE_LATEST_VERSION, + UPDATE_LATEST_VERSION_ERROR, +} from 'types/actions/app'; import AppReducer from 'types/reducer/app'; import { Content, Layout } from './styles'; @@ -13,11 +26,24 @@ import { Content, Layout } from './styles'; function AppLayout(props: AppLayoutProps): JSX.Element { const { isLoggedIn } = useSelector((state) => state.app); const { pathname } = useLocation(); + const { t } = useTranslation(); const [isSignUpPage, setIsSignUpPage] = useState(ROUTES.SIGN_UP === pathname); + const { payload: versionPayload, loading, error: getVersionError } = useFetch( + getVersion, + ); + + const { + payload: latestVersionPayload, + loading: latestLoading, + error: latestError, + } = useFetch(getLatestVersion); + const { children } = props; + const dispatch = useDispatch>(); + useEffect(() => { if (!isLoggedIn) { setIsSignUpPage(true); @@ -27,11 +53,71 @@ function AppLayout(props: AppLayoutProps): JSX.Element { } }, [isLoggedIn, isSignUpPage]); + const latestCurrentCounter = useRef(0); + const latestVersionCounter = useRef(0); + useEffect(() => { if (isLoggedIn && pathname === ROUTES.SIGN_UP) { history.push(ROUTES.APPLICATION); } - }, [isLoggedIn, pathname]); + + if (!latestLoading && latestError && latestCurrentCounter.current === 0) { + latestCurrentCounter.current = 1; + + dispatch({ + type: UPDATE_LATEST_VERSION_ERROR, + payload: { + isError: true, + }, + }); + notification.error({ + message: t('oops_something_went_wrong_version'), + }); + } + + if (!loading && getVersionError && latestVersionCounter.current === 0) { + latestVersionCounter.current = 1; + + dispatch({ + type: UPDATE_CURRENT_ERROR, + payload: { + isError: true, + }, + }); + notification.error({ + message: t('oops_something_went_wrong_version'), + }); + } + + if (!latestLoading && versionPayload) { + dispatch({ + type: UPDATE_CURRENT_VERSION, + payload: { + currentVersion: versionPayload.version, + }, + }); + } + + if (!loading && latestVersionPayload) { + dispatch({ + type: UPDATE_LATEST_VERSION, + payload: { + latestVersion: latestVersionPayload.name, + }, + }); + } + }, [ + dispatch, + loading, + latestLoading, + versionPayload, + latestVersionPayload, + isLoggedIn, + pathname, + getVersionError, + latestError, + t, + ]); return ( diff --git a/frontend/src/container/Header/Breadcrumbs/index.tsx b/frontend/src/container/Header/Breadcrumbs/index.tsx index f1c295e1ff..d88384b75c 100644 --- a/frontend/src/container/Header/Breadcrumbs/index.tsx +++ b/frontend/src/container/Header/Breadcrumbs/index.tsx @@ -11,6 +11,7 @@ const breadcrumbNameMap = { [ROUTES.INSTRUMENTATION]: 'Add instrumentation', [ROUTES.SETTINGS]: 'Settings', [ROUTES.DASHBOARD]: 'Dashboard', + [ROUTES.VERSION]: 'Status', }; function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element { diff --git a/frontend/src/container/SideNav/index.tsx b/frontend/src/container/SideNav/index.tsx index 4902986dbd..44bb2201e8 100644 --- a/frontend/src/container/SideNav/index.tsx +++ b/frontend/src/container/SideNav/index.tsx @@ -1,3 +1,4 @@ +import { CheckCircleTwoTone, WarningOutlined } from '@ant-design/icons'; import { Menu, Typography } from 'antd'; import getLocalStorageKey from 'api/browser/localstorage/get'; import { IS_SIDEBAR_COLLAPSED } from 'constants/app'; @@ -5,6 +6,7 @@ import ROUTES from 'constants/routes'; import history from 'lib/history'; import setTheme, { AppMode } from 'lib/theme/setTheme'; import React, { useCallback, useLayoutEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { connect, useDispatch, useSelector } from 'react-redux'; import { NavLink, useLocation } from 'react-router-dom'; import { bindActionCreators } from 'redux'; @@ -19,11 +21,13 @@ import menus from './menuItems'; import Slack from './Slack'; import { Logo, + RedDot, Sider, SlackButton, SlackMenuItemContainer, ThemeSwitcherWrapper, ToggleButton, + VersionContainer, } from './styles'; function SideNav({ toggleDarkMode }: Props): JSX.Element { @@ -31,9 +35,15 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element { const [collapsed, setCollapsed] = useState( getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true', ); - const { isDarkMode } = useSelector((state) => state.app); + const { + isDarkMode, + currentVersion, + latestVersion, + isCurrentVersionError, + } = useSelector((state) => state.app); const { pathname } = useLocation(); + const { t } = useTranslation(''); const toggleTheme = useCallback(() => { const preMode: AppMode = isDarkMode ? 'lightMode' : 'darkMode'; @@ -77,6 +87,38 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element { window.open('https://signoz.io/slack', '_blank'); }; + const onClickVersionHandler = (): void => { + history.push(ROUTES.VERSION); + }; + + const isNotCurrentVersion = currentVersion !== latestVersion; + + const sidebar = [ + { + onClick: onClickSlackHandler, + icon: , + text: Support, + }, + { + onClick: onClickVersionHandler, + icon: isNotCurrentVersion ? ( + + ) : ( + + ), + text: ( + + {!isCurrentVersionError ? ( + {currentVersion} + ) : ( + {t('n_a')} + )} + {isNotCurrentVersion && } + + ), + }, + ]; + return ( @@ -87,7 +129,7 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element { /> - + {name} ))} - - }> - Support - - + {sidebar.map((props, index) => ( + + + {props.text} + + + ))} ); diff --git a/frontend/src/container/SideNav/styles.ts b/frontend/src/container/SideNav/styles.ts index 0066d2affd..975aa65ba9 100644 --- a/frontend/src/container/SideNav/styles.ts +++ b/frontend/src/container/SideNav/styles.ts @@ -19,6 +19,7 @@ export const Logo = styled.img` interface LogoProps { collapsed: boolean; + index: number; } export const Sider = styled(SiderComponent)` @@ -50,9 +51,10 @@ export const SlackButton = styled(Typography)` export const SlackMenuItemContainer = styled.div` position: fixed; - bottom: 48px; + bottom: ${({ index }): string => `${index * 48 + (index + 16)}px`}; background: #262626; width: ${({ collapsed }): string => (!collapsed ? '200px' : '80px')}; + transition: inherit; &&& { li { @@ -60,11 +62,14 @@ export const SlackMenuItemContainer = styled.div` collapsed && css` padding-left: 24px; + padding-top: 6px; `} } svg { margin-left: ${({ collapsed }): string => (collapsed ? '0' : '24px')}; + width: 28px; + height: 28px; ${({ collapsed }): StyledCSS => collapsed && @@ -73,5 +78,24 @@ export const SlackMenuItemContainer = styled.div` margin: 0 auto; `} } + .ant-menu-title-content { + margin: 0; + } + } +`; + +export const RedDot = styled.div` + width: 12px; + height: 12px; + background: #d32029; + border-radius: 50%; + + margin-left: 1rem; + margin-top: 0.5rem; +`; + +export const VersionContainer = styled.div` + &&& { + display: flex; } `; diff --git a/frontend/src/container/Version/index.tsx b/frontend/src/container/Version/index.tsx new file mode 100644 index 0000000000..e36a0c6d18 --- /dev/null +++ b/frontend/src/container/Version/index.tsx @@ -0,0 +1,110 @@ +import { WarningFilled } from '@ant-design/icons'; +import { Button, Card, Form, Space, Typography } from 'antd'; +import React, { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; + +import { InputComponent } from './styles'; + +const { Title } = Typography; + +function Version(): JSX.Element { + const [form] = Form.useForm(); + const { t } = useTranslation(); + + const onClickUpgradeHandler = useCallback((link: string) => { + window.open(link, '_blank'); + }, []); + + const { + currentVersion, + latestVersion, + isCurrentVersionError, + isLatestVersionError, + } = useSelector((state) => state.app); + + const isLatestVersion = currentVersion === latestVersion; + const isError = isCurrentVersionError || isLatestVersionError; + + return ( + + + {t('version')} + + +
+ + + + + + + + +
+ + {!isError && isLatestVersion && ( +
+ + ✅ + + {t('latest_version_signoz')} + + +
+ )} + + {!isError && !isLatestVersion && ( +
+ + + + + {t('stale_version')} + +
+ )} + + {!isError && !isLatestVersion && ( + + )} +
+ ); +} + +export default Version; diff --git a/frontend/src/container/Version/styles.ts b/frontend/src/container/Version/styles.ts new file mode 100644 index 0000000000..42c7bb1477 --- /dev/null +++ b/frontend/src/container/Version/styles.ts @@ -0,0 +1,8 @@ +import { Input } from 'antd'; +import styled from 'styled-components'; + +export const InputComponent = styled(Input)` + &&& { + max-width: 183px; + } +`; diff --git a/frontend/src/pages/Status/index.tsx b/frontend/src/pages/Status/index.tsx new file mode 100644 index 0000000000..f923b428b4 --- /dev/null +++ b/frontend/src/pages/Status/index.tsx @@ -0,0 +1,8 @@ +import Version from 'container/Version'; +import React from 'react'; + +function Status(): JSX.Element { + return ; +} + +export default Status; diff --git a/frontend/src/store/reducers/app.ts b/frontend/src/store/reducers/app.ts index 2f45bee614..a8feece5a4 100644 --- a/frontend/src/store/reducers/app.ts +++ b/frontend/src/store/reducers/app.ts @@ -7,6 +7,10 @@ import { LOGGED_IN, SIDEBAR_COLLAPSE, SWITCH_DARK_MODE, + UPDATE_CURRENT_ERROR, + UPDATE_CURRENT_VERSION, + UPDATE_LATEST_VERSION, + UPDATE_LATEST_VERSION_ERROR, } from 'types/actions/app'; import InitialValueTypes from 'types/reducer/app'; @@ -14,6 +18,10 @@ const InitialValue: InitialValueTypes = { isDarkMode: getTheme() === 'darkMode', isLoggedIn: getLocalStorageKey(IS_LOGGED_IN) === 'yes', isSideBarCollapsed: getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true', + currentVersion: '', + latestVersion: '', + isCurrentVersionError: false, + isLatestVersionError: false, }; const appReducer = ( @@ -42,6 +50,28 @@ const appReducer = ( }; } + case UPDATE_CURRENT_VERSION: { + return { + ...state, + currentVersion: action.payload.currentVersion, + }; + } + + case UPDATE_LATEST_VERSION: { + return { ...state, latestVersion: action.payload.latestVersion }; + } + + case UPDATE_CURRENT_ERROR: { + return { ...state, isCurrentVersionError: true }; + } + + case UPDATE_LATEST_VERSION_ERROR: { + return { + ...state, + isLatestVersionError: true, + }; + } + default: return state; } diff --git a/frontend/src/types/actions/app.ts b/frontend/src/types/actions/app.ts index f58b589a3d..b07cde36d4 100644 --- a/frontend/src/types/actions/app.ts +++ b/frontend/src/types/actions/app.ts @@ -1,7 +1,15 @@ +import AppReducer from 'types/reducer/app'; + export const SWITCH_DARK_MODE = 'SWITCH_DARK_MODE'; export const LOGGED_IN = 'LOGGED_IN'; export const SIDEBAR_COLLAPSE = 'SIDEBAR_COLLAPSE'; +export const UPDATE_CURRENT_VERSION = 'UPDATE_CURRENT_VERSION'; +export const UPDATE_LATEST_VERSION = 'UPDATE_LATEST_VERSION'; + +export const UPDATE_CURRENT_ERROR = 'UPDATE_CURRENT_ERROR'; +export const UPDATE_LATEST_VERSION_ERROR = 'UPDATE_LATEST_VERSION_ERROR'; + export interface SwitchDarkMode { type: typeof SWITCH_DARK_MODE; } @@ -15,4 +23,31 @@ export interface SideBarCollapse { payload: boolean; } -export type AppAction = SwitchDarkMode | LoggedInUser | SideBarCollapse; +export interface UpdateAppVersion { + type: typeof UPDATE_CURRENT_VERSION; + payload: { + currentVersion: AppReducer['currentVersion']; + }; +} + +export interface UpdateLatestVersion { + type: typeof UPDATE_LATEST_VERSION; + payload: { + latestVersion: AppReducer['latestVersion']; + }; +} + +export interface UpdateVersionError { + type: typeof UPDATE_CURRENT_ERROR | typeof UPDATE_LATEST_VERSION_ERROR; + payload: { + isError: boolean; + }; +} + +export type AppAction = + | SwitchDarkMode + | LoggedInUser + | SideBarCollapse + | UpdateAppVersion + | UpdateLatestVersion + | UpdateVersionError; diff --git a/frontend/src/types/api/user/getLatestVersion.ts b/frontend/src/types/api/user/getLatestVersion.ts new file mode 100644 index 0000000000..043ccb1b45 --- /dev/null +++ b/frontend/src/types/api/user/getLatestVersion.ts @@ -0,0 +1,18 @@ +export interface PayloadProps { + body: string; + created_at: string; + draft: boolean; + html_url: string; + id: number; + mentions_count: number; + name: string; + node_id: number; + prerelease: boolean; + published_at: string; + tag_name: number; + tarball_url: string; + target_commitish: string; + upload_url: string; + url: string; + zipball_url: string; +} diff --git a/frontend/src/types/reducer/app.ts b/frontend/src/types/reducer/app.ts index 36fa886ec9..68af65d075 100644 --- a/frontend/src/types/reducer/app.ts +++ b/frontend/src/types/reducer/app.ts @@ -2,4 +2,8 @@ export default interface AppReducer { isDarkMode: boolean; isLoggedIn: boolean; isSideBarCollapsed: boolean; + currentVersion: string; + latestVersion: string; + isCurrentVersionError: boolean; + isLatestVersionError: boolean; }