From 8ee843abaa0ec08799d86380c34691903cbfe821 Mon Sep 17 00:00:00 2001 From: Praveen D Date: Thu, 26 Jul 2018 15:16:04 +0530 Subject: [PATCH 1/5] console: add update notification --- console/src/Endpoints.js | 2 + console/src/components/ApiExplorer/Actions.js | 1 - console/src/components/Main/Actions.js | 86 ++++++ console/src/components/Main/Main.js | 278 ++++++++++++------ console/src/components/Main/Main.scss | 38 +++ console/src/components/Main/State.js | 2 + console/src/routes.js | 33 ++- 7 files changed, 340 insertions(+), 100 deletions(-) diff --git a/console/src/Endpoints.js b/console/src/Endpoints.js index 90509bd0fd052..9c832ecbf8175 100644 --- a/console/src/Endpoints.js +++ b/console/src/Endpoints.js @@ -12,6 +12,8 @@ const Endpoints = { schemaChange: `${baseUrl}/v1/query`, query: `${baseUrl}/v1/query`, rawSQL: `${baseUrl}/v1/query`, + version: `${baseUrl}/v1/version`, + updateCheck: 'https://releases.hasura.io/graphql-engine', hasuractlMigrate: `${hasuractlUrl}/apis/migrate`, hasuractlMetadata: `${hasuractlUrl}/apis/metadata`, hasuractlMigrateSettings: `${hasuractlUrl}/apis/migrate/settings`, diff --git a/console/src/components/ApiExplorer/Actions.js b/console/src/components/ApiExplorer/Actions.js index 08131de6bdbeb..d04be9e6bf3b4 100644 --- a/console/src/components/ApiExplorer/Actions.js +++ b/console/src/components/ApiExplorer/Actions.js @@ -198,7 +198,6 @@ const graphqlSubscriber = (graphQLParams, url, headers) => { }; return fetcher(graphQLParams); } catch (e) { - console.log(e); return e.json(); } }; diff --git a/console/src/components/Main/Actions.js b/console/src/components/Main/Actions.js index 8fc404940f4fc..f8889705ef833 100644 --- a/console/src/components/Main/Actions.js +++ b/console/src/components/Main/Actions.js @@ -3,6 +3,7 @@ import globals from 'Globals'; import defaultState from './State'; import Endpoints from '../../Endpoints'; import requestAction from '../../utils/requestAction'; +import requestActionPlain from '../../utils/requestActionPlain'; import { globalCookiePolicy } from '../../Endpoints'; import { saveAccessKeyState } from '../AppState'; import { @@ -13,6 +14,11 @@ import { changeRequestHeader } from '../ApiExplorer/Actions'; const SET_MIGRATION_STATUS_SUCCESS = 'Main/SET_MIGRATION_STATUS_SUCCESS'; const SET_MIGRATION_STATUS_ERROR = 'Main/SET_MIGRATION_STATUS_ERROR'; +const SET_SERVER_VERSION_SUCCESS = 'Main/SET_SERVER_VERSION_SUCCESS'; +const SET_SERVER_VERSION_ERROR = 'Main/SET_SERVER_VERSION_ERROR'; +const SET_LATEST_SERVER_VERSION_SUCCESS = + 'Main/SET_LATEST_SERVER_VERSION_SUCCESS'; +const SET_LATEST_SERVER_VERSION_ERROR = 'Main/SET_LATEST_SERVER_VERSION_ERROR'; const UPDATE_MIGRATION_STATUS_SUCCESS = 'Main/UPDATE_MIGRATION_STATUS_SUCCESS'; const UPDATE_MIGRATION_STATUS_ERROR = 'Main/UPDATE_MIGRATION_STATUS_ERROR'; const HASURACTL_URL_ENV = 'Main/HASURACTL_URL_ENV'; @@ -41,6 +47,63 @@ const loadMigrationStatus = () => dispatch => { ); }; +const loadServerVersion = () => dispatch => { + const url = Endpoints.version; + const options = { + method: 'GET', + credentials: globalCookiePolicy, + headers: { 'Content-Type': 'application/json' }, + }; + return dispatch(requestActionPlain(url, options)).then( + data => { + let parsedVersion; + try { + parsedVersion = JSON.parse(data); + } catch (e) { + console.error(e); + } + dispatch({ + type: SET_SERVER_VERSION_SUCCESS, + data: parsedVersion.version, + }); + }, + error => { + console.error(error); + dispatch({ type: SET_SERVER_VERSION_ERROR, data: null }); + } + ); +}; + +const checkServerUpdates = () => (dispatch, getState) => { + const url = + Endpoints.updateCheck + + '?agent=console&version=' + + getState().main.serverVersion; + const options = { + method: 'GET', + credentials: globalCookiePolicy, + headers: { 'Content-Type': 'application/json' }, + }; + return dispatch(requestActionPlain(url, options)).then( + data => { + let parsedVersion; + try { + parsedVersion = JSON.parse(data); + } catch (e) { + console.error(e); + } + dispatch({ + type: SET_LATEST_SERVER_VERSION_SUCCESS, + data: parsedVersion.latest, + }); + }, + error => { + console.error(error); + dispatch({ type: SET_LATEST_SERVER_VERSION_ERROR, data: null }); + } + ); +}; + const validateLogin = isInitialLoad => (dispatch, getState) => { const url = Endpoints.getSchema; const currentSchema = getState().tables.currentSchema; @@ -149,6 +212,27 @@ const mainReducer = (state = defaultState, action) => { ...state, migrationMode: action.data.migration_mode === 'true', }; + case SET_SERVER_VERSION_SUCCESS: + return { + ...state, + serverVersion: action.data, + }; + case SET_SERVER_VERSION_ERROR: + return { + ...state, + serverVersion: null, + }; + + case SET_LATEST_SERVER_VERSION_SUCCESS: + return { + ...state, + latestServerVersion: action.data, + }; + case SET_LATEST_SERVER_VERSION_ERROR: + return { + ...state, + latestServerVersion: null, + }; case UPDATE_MIGRATION_STATUS_SUCCESS: return { ...state, @@ -204,4 +288,6 @@ export { LOGIN_IN_PROGRESS, LOGIN_ERROR, validateLogin, + loadServerVersion, + checkServerUpdates, }; diff --git a/console/src/components/Main/Main.js b/console/src/components/Main/Main.js index 2c3a23dd7a4fd..7cda9f6c5fc06 100644 --- a/console/src/components/Main/Main.js +++ b/console/src/components/Main/Main.js @@ -3,119 +3,203 @@ import { connect } from 'react-redux'; import { Link } from 'react-router'; import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import globals from '../../Globals'; - import * as tooltip from './Tooltips'; import 'react-toggle/style.css'; import Spinner from '../Common/Spinner/Spinner'; -const Main = ({ children, location, migrationModeProgress, currentSchema }) => { - const styles = require('./Main.scss'); - const appPrefix = ''; - const logo = require('./logo.svg'); - const currentLocation = location.pathname; - const currentActiveBlock = currentLocation.split('/')[1]; - - const sidebarClass = styles.sidebar; +const semver = require('semver'); - let mainContent = null; - if (migrationModeProgress) { - mainContent = ( -
- {' '} - {' '} -
- ); - } else { - mainContent = children && React.cloneElement(children); +class Main extends React.Component { + constructor(props) { + super(props); + this.state = { + showBannerNotification: false, + }; + let isUpdateAvailable = false; + try { + isUpdateAvailable = semver.gt( + this.props.latestServerVersion, + this.props.serverVersion + ); + const isClosedBefore = window.localStorage.getItem( + this.props.latestServerVersion + '_BANNER_NOTIFICATION_CLOSED' + ); + if (isClosedBefore === 'true') { + isUpdateAvailable = false; + this.state.showBannerNotification = false; + } else { + this.state.showBannerNotification = isUpdateAvailable; + } + } catch (e) { + console.error(e); + } } - let accessKeyHtml = null; - if (globals.accessKey === '' || globals.accessKey === null) { - accessKeyHtml = ( - - - - - + closeUpdateBanner() { + const { latestServerVersion } = this.props; + window.localStorage.setItem( + latestServerVersion + '_BANNER_NOTIFICATION_CLOSED', + 'true' ); + this.setState({ showBannerNotification: false }); } - return ( -
-
-
-
-
-
+ render() { + const { + children, + location, + migrationModeProgress, + currentSchema, + latestServerVersion, + } = this.props; + const styles = require('./Main.scss'); + const appPrefix = ''; + const logo = require('./logo.svg'); + const currentLocation = location.pathname; + const currentActiveBlock = currentLocation.split('/')[1]; + + const sidebarClass = styles.sidebar; + + let mainContent = null; + if (migrationModeProgress) { + mainContent = ( +
+ {' '} + {' '} +
+ ); + } else { + mainContent = children && React.cloneElement(children); + } + let accessKeyHtml = null; + if (globals.accessKey === '' || globals.accessKey === null) { + accessKeyHtml = ( + + + + + + ); + } + + return ( +
+
+
+
+
+
+ + + +
- +
HASURA
- -
HASURA
-
+
+
    + +
  • + +
    +