mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
console: require async globals before render; track runtime errors (#4449)
This commit is contained in:
parent
d2aac3732a
commit
ea2f1679eb
@ -42,6 +42,9 @@ const globals = {
|
||||
featuresCompatibility: window.__env.serverVersion
|
||||
? getFeaturesCompatibility(window.__env.serverVersion)
|
||||
: null,
|
||||
cliUUID: window.__env.cliUUID,
|
||||
hasuraUUID: '',
|
||||
telemetryNotificationShown: '',
|
||||
isProduction,
|
||||
};
|
||||
|
||||
|
@ -18,43 +18,7 @@ import getRoutes from './routes';
|
||||
|
||||
import reducer from './reducer';
|
||||
import globals from './Globals';
|
||||
import Endpoints from './Endpoints';
|
||||
|
||||
import { filterEventsBlockList, sanitiseUrl } from './telemetryFilter';
|
||||
import { RUN_TIME_ERROR } from './components/Main/Actions';
|
||||
|
||||
/** telemetry **/
|
||||
let analyticsConnection;
|
||||
|
||||
const analyticsUrl = Endpoints.telemetryServer;
|
||||
|
||||
const { consoleMode, enableTelemetry, cliUUID } = window.__env;
|
||||
|
||||
const telemetryEnabled =
|
||||
enableTelemetry !== undefined && enableTelemetry === true;
|
||||
|
||||
if (telemetryEnabled) {
|
||||
try {
|
||||
analyticsConnection = new WebSocket(analyticsUrl);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
const onError = error => {
|
||||
console.error('WebSocket Error for Events' + error);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const onClose = () => {
|
||||
try {
|
||||
analyticsConnection = new WebSocket(analyticsUrl);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
analyticsConnection.onclose = onClose();
|
||||
analyticsConnection.onerror = onError();
|
||||
};
|
||||
import { trackReduxAction } from './telemetry';
|
||||
|
||||
function analyticsLogger({ getState }) {
|
||||
return next => action => {
|
||||
@ -62,53 +26,9 @@ function analyticsLogger({ getState }) {
|
||||
const returnValue = next(action);
|
||||
|
||||
// check if analytics tracking is enabled
|
||||
if (telemetryEnabled) {
|
||||
const actionType = action.type;
|
||||
|
||||
// filter events
|
||||
if (!filterEventsBlockList.includes(actionType)) {
|
||||
// When the connection is open, send data to the server
|
||||
if (
|
||||
analyticsConnection &&
|
||||
analyticsConnection.readyState === analyticsConnection.OPEN
|
||||
) {
|
||||
const serverVersion = getState().main.serverVersion;
|
||||
const url = sanitiseUrl(window.location.pathname);
|
||||
|
||||
const reqBody = {
|
||||
server_version: serverVersion,
|
||||
event_type: actionType,
|
||||
url,
|
||||
console_mode: consoleMode,
|
||||
cli_uuid: cliUUID,
|
||||
server_uuid: getState().telemetry.hasura_uuid,
|
||||
};
|
||||
|
||||
const isLocationType = actionType === '@@router/LOCATION_CHANGE';
|
||||
if (isLocationType) {
|
||||
// capture page views
|
||||
const payload = action.payload;
|
||||
reqBody.url = sanitiseUrl(payload.pathname);
|
||||
}
|
||||
|
||||
const isErrorType = actionType === RUN_TIME_ERROR;
|
||||
if (isErrorType) {
|
||||
reqBody.data = action.data;
|
||||
}
|
||||
|
||||
// Send the data
|
||||
analyticsConnection.send(
|
||||
JSON.stringify({ data: reqBody, topic: globals.telemetryTopic })
|
||||
);
|
||||
|
||||
// check for possible error events and store more data?
|
||||
} else {
|
||||
// retry websocket connection
|
||||
// analyticsConnection = new WebSocket(analyticsUrl);
|
||||
}
|
||||
}
|
||||
if (globals.enableTelemetry) {
|
||||
trackReduxAction(action, getState);
|
||||
}
|
||||
|
||||
// This will likely be the action itself, unless
|
||||
// a middleware further in chain changed it.
|
||||
return returnValue;
|
||||
|
@ -1,5 +1,7 @@
|
||||
import defaultState from './State';
|
||||
import Notifications from 'react-notification-system-redux';
|
||||
import { loadConsoleOpts } from '../../telemetry/Actions';
|
||||
import { fetchServerConfig } from '../Main/Actions';
|
||||
|
||||
const LOAD_REQUEST = 'App/ONGOING_REQUEST';
|
||||
const DONE_REQUEST = 'App/DONE_REQUEST';
|
||||
@ -46,6 +48,15 @@ const showNotification = ({
|
||||
};
|
||||
};
|
||||
|
||||
export const requireAsyncGlobals = ({ dispatch }) => {
|
||||
return (nextState, finalState, callback) => {
|
||||
Promise.all([
|
||||
dispatch(loadConsoleOpts()),
|
||||
dispatch(fetchServerConfig()),
|
||||
]).finally(callback);
|
||||
};
|
||||
};
|
||||
|
||||
const progressBarReducer = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
case LOAD_REQUEST:
|
||||
@ -93,6 +104,7 @@ const progressBarReducer = (state = defaultState, action) => {
|
||||
...state,
|
||||
modalOpen: true,
|
||||
error: true,
|
||||
ongoingRequest: false,
|
||||
connectionFailed: true,
|
||||
};
|
||||
default:
|
||||
|
@ -1,5 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ProgressBar from 'react-progress-bar-plus';
|
||||
import Notifications from 'react-notification-system-redux';
|
||||
@ -7,29 +7,33 @@ import { hot } from 'react-hot-loader';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import ErrorBoundary from '../Error/ErrorBoundary';
|
||||
import {
|
||||
loadConsoleOpts,
|
||||
telemetryNotificationShown,
|
||||
} from '../../telemetry/Actions';
|
||||
import { telemetryNotificationShown } from '../../telemetry/Actions';
|
||||
import { showTelemetryNotification } from '../../telemetry/Notifications';
|
||||
import globals from '../../Globals';
|
||||
import styles from './App.scss';
|
||||
|
||||
import { theme } from '../UIKit/theme';
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
const { dispatch } = this.props;
|
||||
export const GlobalContext = React.createContext(globals);
|
||||
|
||||
// Hide the loader once the react component is ready.
|
||||
// NOTE: This will execute only once (since this is the parent component for all other components).
|
||||
const App = ({
|
||||
ongoingRequest,
|
||||
percent,
|
||||
intervalTime,
|
||||
children,
|
||||
notifications,
|
||||
connectionFailed,
|
||||
dispatch,
|
||||
metadata,
|
||||
telemetry,
|
||||
}) => {
|
||||
React.useEffect(() => {
|
||||
const className = document.getElementById('content').className;
|
||||
document.getElementById('content').className = className + ' show';
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}, []);
|
||||
|
||||
dispatch(loadConsoleOpts());
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { telemetry, dispatch } = this.props;
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
telemetry.console_opts &&
|
||||
!telemetry.console_opts.telemetryNotificationShown
|
||||
@ -37,44 +41,25 @@ class App extends Component {
|
||||
dispatch(telemetryNotificationShown());
|
||||
dispatch(showTelemetryNotification());
|
||||
}
|
||||
}, [telemetry]);
|
||||
|
||||
let connectionFailMsg = null;
|
||||
if (connectionFailed) {
|
||||
connectionFailMsg = (
|
||||
<div
|
||||
className={`${styles.alertDanger} ${styles.remove_margin_bottom} alert alert-danger `}
|
||||
>
|
||||
<strong>
|
||||
Hasura console is not able to reach your Hasura GraphQL engine
|
||||
instance. Please ensure that your instance is running and the endpoint
|
||||
is configured correctly.
|
||||
</strong>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const styles = require('./App.scss');
|
||||
const {
|
||||
requestError,
|
||||
error,
|
||||
ongoingRequest,
|
||||
percent,
|
||||
intervalTime,
|
||||
children,
|
||||
notifications,
|
||||
connectionFailed,
|
||||
dispatch,
|
||||
metadata,
|
||||
} = this.props;
|
||||
|
||||
if (requestError && error) {
|
||||
// console.error(requestError, error);
|
||||
}
|
||||
|
||||
let connectionFailMsg = null;
|
||||
if (connectionFailed) {
|
||||
connectionFailMsg = (
|
||||
<div
|
||||
style={{ marginBottom: '0px' }}
|
||||
className={styles.alertDanger + ' alert alert-danger'}
|
||||
>
|
||||
<strong>
|
||||
Hasura console is not able to reach your Hasura GraphQL engine
|
||||
instance. Please ensure that your instance is running and the
|
||||
endpoint is configured correctly.
|
||||
</strong>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
return (
|
||||
<GlobalContext.Provider value={globals}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<ErrorBoundary metadata={metadata} dispatch={dispatch}>
|
||||
<div>
|
||||
@ -92,18 +77,16 @@ class App extends Component {
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
</GlobalContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
App.propTypes = {
|
||||
reqURL: PropTypes.string,
|
||||
reqData: PropTypes.object,
|
||||
statusCode: PropTypes.number,
|
||||
|
||||
error: PropTypes.object,
|
||||
ongoingRequest: PropTypes.bool,
|
||||
requestError: PropTypes.bool,
|
||||
connectionFailed: PropTypes.bool,
|
||||
|
||||
intervalTime: PropTypes.number,
|
||||
|
@ -2,6 +2,9 @@ import React from 'react';
|
||||
|
||||
import styles from '../Common.scss';
|
||||
|
||||
import { GlobalContext } from '../../App/App';
|
||||
import { trackRuntimeError } from '../../../telemetry';
|
||||
|
||||
/*
|
||||
This is a Button HOC that takes all the props supported by <button>
|
||||
- color(default: white): color of the button; currently supports yellow, red, green, gray and white
|
||||
@ -16,7 +19,7 @@ export interface ButtonProps extends React.ComponentProps<'button'> {
|
||||
}
|
||||
|
||||
const Button: React.FC<ButtonProps> = props => {
|
||||
const { children, size, color, className, type = 'button' } = props;
|
||||
const { children, onClick, size, color, className, type = 'button' } = props;
|
||||
let extendedClassName = `${className || ''} btn ${
|
||||
size ? `btn-${size} ` : 'button '
|
||||
}`;
|
||||
@ -37,8 +40,29 @@ const Button: React.FC<ButtonProps> = props => {
|
||||
extendedClassName += 'btn-default';
|
||||
break;
|
||||
}
|
||||
|
||||
const globals = React.useContext(GlobalContext);
|
||||
|
||||
const trackedOnClick = (
|
||||
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||
) => {
|
||||
try {
|
||||
if (onClick) {
|
||||
onClick(e);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
trackRuntimeError(globals, error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button {...props} className={extendedClassName} type={type}>
|
||||
<button
|
||||
{...props}
|
||||
className={extendedClassName}
|
||||
type={type}
|
||||
onClick={onClick ? trackedOnClick : undefined}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
@ -1,7 +1,5 @@
|
||||
// TODO: make functions from this file available without imports
|
||||
|
||||
import { showErrorNotification } from '../../Services/Common/Notification';
|
||||
|
||||
/* TYPE utils */
|
||||
|
||||
export const isNotDefined = value => {
|
||||
@ -233,8 +231,9 @@ export const getConfirmation = (
|
||||
export const uploadFile = (
|
||||
fileHandler,
|
||||
fileFormat = null,
|
||||
invalidFileHandler = null
|
||||
) => dispatch => {
|
||||
invalidFileHandler = null,
|
||||
errorCallback = null
|
||||
) => {
|
||||
const fileInputElement = document.createElement('div');
|
||||
fileInputElement.innerHTML = '<input style="display:none" type="file">';
|
||||
const fileInput = fileInputElement.firstChild;
|
||||
@ -254,12 +253,12 @@ export const uploadFile = (
|
||||
if (invalidFileHandler) {
|
||||
invalidFileHandler(fileName);
|
||||
} else {
|
||||
dispatch(
|
||||
showErrorNotification(
|
||||
if (errorCallback) {
|
||||
errorCallback(
|
||||
'Invalid file format',
|
||||
`Expected a ${expectedFileSuffix} file`
|
||||
)
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fileInputElement.remove();
|
||||
|
@ -315,10 +315,20 @@ export const getFetchManualTriggersQuery = tableName => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const getConsoleOptsQuery = () =>
|
||||
generateSelectQuery(
|
||||
'select',
|
||||
{ name: 'hdb_version', schema: 'hdb_catalog' },
|
||||
{
|
||||
columns: ['hasura_uuid', 'console_state'],
|
||||
}
|
||||
);
|
||||
|
||||
export const getSaveRemoteRelQuery = (args, isNew) => ({
|
||||
type: `${isNew ? 'create' : 'update'}_remote_relationship`,
|
||||
args,
|
||||
});
|
||||
|
||||
export const getDropRemoteRelQuery = (name, table) => ({
|
||||
type: 'delete_remote_relationship',
|
||||
args: {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import defaultState from './State';
|
||||
import globals from '../../Globals';
|
||||
import requestAction from '../../utils/requestAction';
|
||||
import requestActionPlain from '../../utils/requestActionPlain';
|
||||
import Endpoints, { globalCookiePolicy } from '../../Endpoints';
|
||||
@ -121,16 +122,19 @@ const fetchServerConfig = () => (dispatch, getState) => {
|
||||
});
|
||||
return dispatch(requestAction(url, options)).then(
|
||||
data => {
|
||||
return dispatch({
|
||||
dispatch({
|
||||
type: SERVER_CONFIG_FETCH_SUCCESS,
|
||||
data: data,
|
||||
});
|
||||
globals.serverConfig = data;
|
||||
return Promise.resolve();
|
||||
},
|
||||
error => {
|
||||
return dispatch({
|
||||
dispatch({
|
||||
type: SERVER_CONFIG_FETCH_FAIL,
|
||||
data: error,
|
||||
});
|
||||
return Promise.reject();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -33,8 +33,6 @@ class ImportMetadata extends Component {
|
||||
render() {
|
||||
const styles = require('../Settings.scss');
|
||||
|
||||
const { dispatch } = this.props;
|
||||
|
||||
const { isImporting } = this.state;
|
||||
|
||||
const handleImport = e => {
|
||||
@ -42,7 +40,7 @@ class ImportMetadata extends Component {
|
||||
|
||||
this.setState({ isImporting: true });
|
||||
|
||||
dispatch(uploadFile(this.importMetadata, 'json'));
|
||||
uploadFile(this.importMetadata, 'json');
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -2,3 +2,4 @@ export const SERVER_CONSOLE_MODE = 'server';
|
||||
export const CLI_CONSOLE_MODE = 'cli';
|
||||
|
||||
export const ADMIN_SECRET_HEADER_KEY = 'x-hasura-admin-secret';
|
||||
export const REDUX_LOCATION_CHANGE_ACTION_TYPE = '@@router/LOCATION_CHANGE';
|
||||
|
@ -3,12 +3,14 @@ import { Route, IndexRoute, IndexRedirect } from 'react-router';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { App, Main, PageNotFound } from 'components';
|
||||
|
||||
import globals from './Globals';
|
||||
|
||||
import { App, Main, PageNotFound } from 'components';
|
||||
|
||||
import validateLogin from './utils/validateLogin';
|
||||
|
||||
import { requireAsyncGlobals } from './components/App/Actions';
|
||||
|
||||
import { composeOnEnterHooks } from 'utils/router';
|
||||
|
||||
import { loadMigrationStatus } from './components/Main/Actions';
|
||||
@ -103,7 +105,14 @@ const routes = store => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Route path="/" component={App} onEnter={validateLogin(store)}>
|
||||
<Route
|
||||
path="/"
|
||||
component={App}
|
||||
onEnter={composeOnEnterHooks([
|
||||
validateLogin(store),
|
||||
requireAsyncGlobals(store),
|
||||
])}
|
||||
>
|
||||
<Route path="login" component={generatedLoginConnector(connect)} />
|
||||
<Route
|
||||
path=""
|
||||
|
@ -2,11 +2,15 @@ import Endpoints, { globalCookiePolicy } from '../Endpoints';
|
||||
import requestAction from '../utils/requestAction';
|
||||
import dataHeaders from '../components/Services/Data/Common/Headers';
|
||||
import defaultTelemetryState from './State';
|
||||
import { getRunSqlQuery } from '../components/Common/utils/v1QueryUtils';
|
||||
import {
|
||||
getRunSqlQuery,
|
||||
getConsoleOptsQuery,
|
||||
} from '../components/Common/utils/v1QueryUtils';
|
||||
import {
|
||||
showErrorNotification,
|
||||
showSuccessNotification,
|
||||
} from '../components/Services/Common/Notification';
|
||||
import globals from '../Globals';
|
||||
|
||||
const SET_CONSOLE_OPTS = 'Telemetry/SET_CONSOLE_OPTS';
|
||||
const SET_NOTIFICATION_SHOWN = 'Telemetry/SET_NOTIFICATION_SHOWN';
|
||||
@ -98,23 +102,14 @@ const setPreReleaseNotificationOptOutInDB = () => dispatch => {
|
||||
return dispatch(setConsoleOptsInDB(opts, successCb, errorCb));
|
||||
};
|
||||
|
||||
const loadConsoleOpts = () => {
|
||||
export const loadConsoleOpts = () => {
|
||||
return (dispatch, getState) => {
|
||||
const url = Endpoints.getSchema;
|
||||
const options = {
|
||||
credentials: globalCookiePolicy,
|
||||
method: 'POST',
|
||||
headers: dataHeaders(getState),
|
||||
body: JSON.stringify({
|
||||
type: 'select',
|
||||
args: {
|
||||
table: {
|
||||
name: 'hdb_version',
|
||||
schema: 'hdb_catalog',
|
||||
},
|
||||
columns: ['hasura_uuid', 'console_state'],
|
||||
},
|
||||
}),
|
||||
body: JSON.stringify(getConsoleOptsQuery()),
|
||||
};
|
||||
|
||||
return dispatch(requestAction(url, options)).then(
|
||||
@ -124,21 +119,34 @@ const loadConsoleOpts = () => {
|
||||
type: SET_HASURA_UUID,
|
||||
data: data[0].hasura_uuid,
|
||||
});
|
||||
globals.hasuraUUID = data[0].hasura_uuid;
|
||||
dispatch({
|
||||
type: SET_CONSOLE_OPTS,
|
||||
data: data[0].console_state,
|
||||
});
|
||||
globals.telemetryNotificationShown =
|
||||
data[0].console_state.telemetryNotificationShown;
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
error => {
|
||||
console.error(
|
||||
'Failed to load console options: ' + JSON.stringify(error)
|
||||
);
|
||||
return Promise.reject();
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export const requireConsoleOpts = ({ dispatch }) => (
|
||||
nextState,
|
||||
replaceState,
|
||||
callback
|
||||
) => {
|
||||
dispatch(loadConsoleOpts()).finally(callback);
|
||||
};
|
||||
|
||||
const telemetryReducer = (state = defaultTelemetryState, action) => {
|
||||
switch (action.type) {
|
||||
case SET_CONSOLE_OPTS:
|
||||
@ -168,7 +176,6 @@ const telemetryReducer = (state = defaultTelemetryState, action) => {
|
||||
|
||||
export default telemetryReducer;
|
||||
export {
|
||||
loadConsoleOpts,
|
||||
telemetryNotificationShown,
|
||||
setPreReleaseNotificationOptOutInDB,
|
||||
setTelemetryNotificationShownInDB,
|
||||
|
70
console/src/telemetry/filters.ts
Normal file
70
console/src/telemetry/filters.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import globals from '../Globals';
|
||||
|
||||
const filterEventsBlockList = [
|
||||
'App/ONGOING_REQUEST',
|
||||
'App/DONE_REQUEST',
|
||||
'App/FAILED_REQUEST',
|
||||
'App/ERROR_REQUEST',
|
||||
'RNS_SHOW_NOTIFICATION',
|
||||
'RNS_HIDE_NOTIFICATION',
|
||||
'RNS_REMOVE_ALL_NOTIFICATIONS',
|
||||
];
|
||||
|
||||
const DATA_PATH = '/data';
|
||||
const API_EXPLORER_PATH = '/api-explorer';
|
||||
const REMOTE_SCHEMAS_PATH = '/remote-schemas';
|
||||
const EVENTS_PATH = '/events';
|
||||
|
||||
const dataHandler = (path: string) => {
|
||||
return (
|
||||
DATA_PATH +
|
||||
path
|
||||
.replace(/\/schema\/([^/]*)(\/)?/, '/schema/SCHEMA_NAME$2')
|
||||
.replace(
|
||||
/(\/schema\/.*)\/tables\/([^/]*)(\/.*)?/,
|
||||
'$1/tables/TABLE_NAME$3'
|
||||
)
|
||||
.replace(/(\/schema\/.*)\/views\/([^/]*)(\/.*)?/, '$1/views/VIEW_NAME$3')
|
||||
.replace(
|
||||
/(\/schema\/.*)\/functions\/([^/]*)(\/.*)?/,
|
||||
'$1/functions/FUNCTION_NAME$3'
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const apiExplorerHandler = () => {
|
||||
return API_EXPLORER_PATH;
|
||||
};
|
||||
|
||||
const remoteSchemasHandler = (path: string) => {
|
||||
return (
|
||||
REMOTE_SCHEMAS_PATH +
|
||||
path.replace(/(\/manage\/)[^/]*(\/\w+.*)$/, '$1REMOTE_SCHEMA_NAME$2')
|
||||
);
|
||||
};
|
||||
|
||||
const eventsHandler = (path: string) => {
|
||||
return (
|
||||
EVENTS_PATH +
|
||||
path.replace(/(\/manage\/triggers\/)[^/]*(\/\w+.*)$/, '$1TRIGGER_NAME$2')
|
||||
);
|
||||
};
|
||||
|
||||
const sanitiseUrl = (rawPath: string) => {
|
||||
const path = rawPath.replace(new RegExp(globals.urlPrefix, 'g'), '');
|
||||
if (path.indexOf(DATA_PATH) === 0) {
|
||||
return dataHandler(path.slice(DATA_PATH.length));
|
||||
}
|
||||
if (path.indexOf(API_EXPLORER_PATH) === 0) {
|
||||
return apiExplorerHandler();
|
||||
}
|
||||
if (path.indexOf(REMOTE_SCHEMAS_PATH) === 0) {
|
||||
return remoteSchemasHandler(path.slice(REMOTE_SCHEMAS_PATH.length));
|
||||
}
|
||||
if (path.indexOf(EVENTS_PATH) === 0) {
|
||||
return eventsHandler(path.slice(EVENTS_PATH.length));
|
||||
}
|
||||
return '/';
|
||||
};
|
||||
|
||||
export { filterEventsBlockList, sanitiseUrl };
|
105
console/src/telemetry/index.ts
Normal file
105
console/src/telemetry/index.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import endpoints from '../Endpoints';
|
||||
import globals from '../Globals';
|
||||
import { filterEventsBlockList, sanitiseUrl } from './filters';
|
||||
import { RUN_TIME_ERROR } from '../components/Main/Actions';
|
||||
import { REDUX_LOCATION_CHANGE_ACTION_TYPE } from '../constants';
|
||||
import { GetReduxState, ReduxAction } from '../types';
|
||||
|
||||
interface TelemetryGlobals {
|
||||
serverVersion: string;
|
||||
consoleMode: string;
|
||||
cliUUID: string;
|
||||
hasuraUUID: string;
|
||||
}
|
||||
|
||||
const createClient = () => {
|
||||
if (globals.enableTelemetry) {
|
||||
try {
|
||||
const client = new WebSocket(endpoints.telemetryServer);
|
||||
client.onerror = e => {
|
||||
console.error(`WebSocket Error for Events${e}`);
|
||||
};
|
||||
return client;
|
||||
} catch (e) {
|
||||
console.error('Unable to initialise telemetry client', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
let client = createClient();
|
||||
if (client) {
|
||||
const onClose = () => {
|
||||
client = createClient();
|
||||
if (client) {
|
||||
client.onclose = onClose;
|
||||
}
|
||||
};
|
||||
client.onclose = onClose;
|
||||
}
|
||||
|
||||
const isTelemetryConnectionReady = () => {
|
||||
return !!(client && client.readyState === client.OPEN);
|
||||
};
|
||||
|
||||
const sendEvent = (payload: any) => {
|
||||
if (client && isTelemetryConnectionReady()) {
|
||||
client.send(
|
||||
JSON.stringify({ data: payload, topic: globals.telemetryTopic })
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const trackReduxAction = (
|
||||
action: ReduxAction,
|
||||
getState: GetReduxState
|
||||
) => {
|
||||
const actionType = action.type;
|
||||
// filter events
|
||||
if (!filterEventsBlockList.includes(actionType)) {
|
||||
const serverVersion = getState().main.serverVersion;
|
||||
const url = sanitiseUrl(window.location.pathname);
|
||||
|
||||
const reqBody = {
|
||||
server_version: serverVersion,
|
||||
event_type: actionType,
|
||||
url,
|
||||
console_mode: globals.consoleMode,
|
||||
cli_uuid: globals.cliUUID,
|
||||
server_uuid: getState().telemetry.hasura_uuid,
|
||||
data: null,
|
||||
};
|
||||
|
||||
const isLocationType = actionType === REDUX_LOCATION_CHANGE_ACTION_TYPE;
|
||||
if (isLocationType) {
|
||||
// capture page views
|
||||
const payload = action.payload;
|
||||
reqBody.url = sanitiseUrl(payload.pathname);
|
||||
}
|
||||
|
||||
const isErrorType = actionType === RUN_TIME_ERROR;
|
||||
if (isErrorType) {
|
||||
reqBody.data = action.data;
|
||||
}
|
||||
|
||||
// Send the data
|
||||
sendEvent(reqBody);
|
||||
}
|
||||
};
|
||||
|
||||
export const trackRuntimeError = (
|
||||
telemeteryGlobals: TelemetryGlobals,
|
||||
error: Error
|
||||
) => {
|
||||
const reqBody = {
|
||||
server_version: telemeteryGlobals.serverVersion,
|
||||
event_type: RUN_TIME_ERROR,
|
||||
url: sanitiseUrl(window.location.pathname),
|
||||
console_mode: telemeteryGlobals.consoleMode,
|
||||
cli_uuid: telemeteryGlobals.cliUUID,
|
||||
server_uuid: telemeteryGlobals.hasuraUUID,
|
||||
data: { message: error.message, stack: error.stack },
|
||||
};
|
||||
sendEvent(reqBody);
|
||||
};
|
9
console/src/types.ts
Normal file
9
console/src/types.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export type ReduxAction = {
|
||||
type: string;
|
||||
payload: {
|
||||
pathname: string;
|
||||
};
|
||||
data: any;
|
||||
};
|
||||
|
||||
export type GetReduxState = () => Record<string, any>;
|
@ -7,6 +7,7 @@ import {
|
||||
DONE_REQUEST,
|
||||
FAILED_REQUEST,
|
||||
ERROR_REQUEST,
|
||||
CONNECTION_FAILED,
|
||||
} from '../components/App/Actions';
|
||||
import { globalCookiePolicy } from '../Endpoints';
|
||||
|
||||
@ -73,7 +74,7 @@ const requestAction = (
|
||||
},
|
||||
error => {
|
||||
console.error('Request error: ', error);
|
||||
dispatch({ type: FAILED_REQUEST });
|
||||
dispatch({ type: CONNECTION_FAILED });
|
||||
if (ERROR) {
|
||||
dispatch({
|
||||
type: ERROR,
|
||||
|
Loading…
Reference in New Issue
Block a user