Skip to content

Commit

Permalink
V0.12.2 (#130)
Browse files Browse the repository at this point in the history
* fixes #127 standalone 3.5 shouldn't show DB LEADER role

* refactor SingleMetricChart out of MetricsChart (related to #126)

* factor out encountered neo4j errors

* implement value markers on metric charts (issue #126)

* #126 better formatting for Y axis values

* addresses #128 excessive error logging

* upgrade sentry dependencies

* v0.12.2 release notes

* use ScatterCharts in metrics

* refactor node->member component property naming

* simplify and remove vestigal code

* simplify greatly and move to filterable table layout
  • Loading branch information
moxious authored Jan 24, 2020
1 parent 0dd6b2f commit d086c5d
Show file tree
Hide file tree
Showing 25 changed files with 415 additions and 483 deletions.
6 changes: 5 additions & 1 deletion create-neo4j4.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ docker stop neo4j-empty

PASSWORD=admin
CWD=`pwd`
NEO4J=neo4j:4.0.0-alpha09mr02-enterprise
NEO4J=neo4j:4.0.0-enterprise

docker run -d --name neo4j-empty --rm \
-p 127.0.0.1:7474:7474 \
-p 127.0.0.1:7687:7687 \
--volume $HOME/neo4j/core1/plugins:/plugins \
--env=apoc.export.file.enabled=true \
--env=apoc.import.file.enabled=true \
--env=NEO4J_dbms_security_procedures_unrestricted=apoc.\* \
--env=NEO4J_ACCEPT_LICENSE_AGREEMENT=yes \
--env=NEO4J_dbms_memory_pagecache_size=1G \
--env=NEO4J_dbms_memory_heap_initial__size=2G \
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "halin",
"description": "Halin helps you monitor and improve your Neo4j graph",
"version": "0.12.1",
"version": "0.12.2",
"neo4jDesktop": {
"apiVersion": "^1.2.0"
},
Expand All @@ -19,7 +19,7 @@
"homepage": "http://halin.graphapp.io/",
"private": false,
"dependencies": {
"@sentry/browser": "^4.4.1",
"@sentry/browser": "^5.11.1",
"autobind-decorator": "2.2.1",
"bluebird": "^3.7.1",
"generic-pool": "^3.6.1",
Expand Down
8 changes: 8 additions & 0 deletions release-notes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Halin Release Notes

## 0.12.2 Patch Release

- Fixes #127: Neo4j 3.5 standalone members appear properly, not as LEADER
- Fixes #126: Metric chart improvements for readability & precision
- Fixes #128: excessive error reporting
- Upgrades to sentry dependencies
- Simplify the data feed statistics page into a table

## 0.12.1 Patch Release

- Fixes #121 identify halin with global user-agent string in bolt driver
Expand Down
2 changes: 1 addition & 1 deletion src/api/Database.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default class Database {
name: Database.SINGLEDB_NAME,
currentStatus: Database.STATUS_ONLINE,
requestedStatus: Database.STATUS_ONLINE,
role: 'LEADER',
role: ClusterMember.ROLE_STANDALONE,
default: true,
error: '',
address: halin ? halin.getBaseURI() : '0.0.0.0',
Expand Down
6 changes: 6 additions & 0 deletions src/api/Database.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Database from './Database';
import ClusterMember from './cluster/ClusterMember';
import HalinContext from './HalinContext';
import fakes from './../testutils/fakes';
import neo4j from './driver/index';
Expand Down Expand Up @@ -83,6 +84,11 @@ describe('Database', function() {
expect(d).toBeTruthy();
expect(d.name).toEqual(Database.SINGLEDB_NAME);
expect(d.isOnline()).toBeTruthy();
const memberStatuses = d.getMemberStatuses();
expect(memberStatuses.length).toBe(1);
const stat = memberStatuses[0];
expect(stat.name).toBe(Database.SINGLEDB_NAME);
expect(stat.role).toBe(ClusterMember.ROLE_STANDALONE);
expect(d.isDefault()).toBeTruthy();
});

Expand Down
11 changes: 11 additions & 0 deletions src/api/cluster/ClusterMember.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export default class ClusterMember {
this.driver = null;
this.observations = new Ring(MAX_OBSERVATIONS);
this.errors = {};
this.pluggedIn = true; // Whether or not the member is noticeably online and responsive.
}

/**
Expand Down Expand Up @@ -475,6 +476,7 @@ export default class ClusterMember {

return featureProbes.runAllProbes(this)
.then(dbms => {
this.pluggedIn = true;
this.dbms = dbms;
})
.then(() => {
Expand All @@ -495,12 +497,15 @@ export default class ClusterMember {
});
}

isOnline() { return this.pluggedIn; }

/**
* This function just takes note of a transaction success, as a data point/observation,
* so that we can track ongoing responsiveness/performance.
* @param {Number} time number of ms elapsed
*/
_txSuccess(time) {
this.pluggedIn = true;
// It's a ring not an array, so it cannot grow without bound.
this.observations.push({ x: new Date(), y: time });
}
Expand All @@ -512,6 +517,12 @@ export default class ClusterMember {
* @param {Error} err
*/
_txError(err) {
if(neo4jErrors.failedToEstablishConnection(err) ||
neo4jErrors.repeatedAuthFailure(err) ||
neo4jErrors.connectionRefused(err)) {
this.pluggedIn = false;
}

const str = `${err}`;
if (_.has(this.errors, str)) {
this.errors[str] = this.errors[str] + 1;
Expand Down
2 changes: 1 addition & 1 deletion src/api/cluster/ClusterMemberSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export default class ClusterMemberSet {
};

const onError = (err, dataFeed) => {
sentry.error('ClusterMemberSet: failed to ping', addr, err);
sentry.fine('ClusterMemberSet: failed to ping', addr, err);
reject(err, dataFeed);
};

Expand Down
11 changes: 10 additions & 1 deletion src/api/data/DataFeed.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,16 @@ export default class DataFeed extends Metric {
return this.listeners.map(listener => listener(this.state, this));
})
.catch(err => {
sentry.reportError(err, 'Failed to execute timeseries query');
// About this catch block, it's possible for halin to be stuck in a loop,
// continuously getting the same error as we poll. This is common if the DB
// got unplugged, crashed, or if it has some internal config error. So we'll
// report certian errors the first time we see them, and not spam every time
// as we poll.
if (`${this.state.error}` !== `${err}`) {
sentry.reportError(err, 'Failed to execute timeseries query (first time)');
} else {
sentry.fine(`Duplicate polled error in timeseries ${err}`);
}

this.state.lastDataArrived = this.feedStartTime;
this.state.error = err;
Expand Down
3 changes: 3 additions & 0 deletions src/api/driver/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@ export default {
contains: (err, str) => asStr(err).toLowerCase().indexOf(str.toLowerCase()) > -1,
insecureWSFromHTTPS: (err) => asStr(err).toLowerCase().indexOf('insecure websocket connection may not be initiated from a page loaded over HTTPS') > -1,
repeatedAuthFailure: (err) => asStr(err).toLowerCase().indexOf('incorrect authentication details too many times in a row') > -1,
fileNotFound: (err) => asStr(err).indexOf('java.io.FileNotFoundException') > -1,
connectionRefused: (err) => asStr(err).indexOf('ERR_CONNECTION_REFUSED') > -1,
apocFileImportNotEnabled: (err) => asStr(err).indexOf('apoc.import.file.enabled') > -1,
};

24 changes: 15 additions & 9 deletions src/api/sentry/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ const init = () => {

return Sentry.init({
dsn,
maxBreadcrumbs: 50,
maxBreadcrumbs: 10,
debug: false,
release: appPkg.version,
});
});
};

const info = (...args) => enabled ? console.log('INFO', ...args) : null;
Expand All @@ -28,14 +28,20 @@ const fine = (...args) => enabled ? console.log('FINE', ...args) : null;
const debug = (...args) => enabled ? console.log('DEBUG', ...args) : null;

// Filter out certain messages which might be so common that they'd create problems.
const shouldSentryCapture = err => {
const str = `${err}`;
// TBD pending further implementation
const shouldSentryCapture = err => true;

if (str.indexOf('WebSocket connection failure') > -1) {
return false;
}
const context = ctx => {
const eventMetadata = {
neo4j: ctx.getVersion(),
clustered: ctx.isCluster(),
base: ctx.getBaseURI(),
};

return true;
// https://docs.sentry.io/platforms/javascript/#extra-context
Object.keys(eventMetadata).forEach(key => {
Sentry.setExtra(key, eventMetadata[key]);
});
}

const reportError = (err, message=null) => {
Expand All @@ -57,7 +63,7 @@ const disable = () => {
};

export default {
init, reportError,
init, reportError, context,
info, warn, error, fine, debug,
disable,
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { Component } from 'react';
import _ from 'lodash';

import queryLibrary from '../../../api/data/queries/query-library';

import PropTypes from 'prop-types';
import hoc from '../../higherOrderComponents';
import CypherDataTable from '../../data/CypherDataTable/CypherDataTable';
import Explainer from '../../ui/scaffold/Explainer/Explainer';
Expand Down Expand Up @@ -51,7 +51,7 @@ class Neo4jConfiguration extends Component {
<h3>Neo4j Configuration <Explainer knowledgebase='Neo4jConfiguration' /></h3>

<CypherDataTable
node={this.props.node}
node={this.props.member}
query={this.state.query}
allowDownloadCSV={true}
displayColumns={this.state.displayColumns}
Expand All @@ -62,4 +62,8 @@ class Neo4jConfiguration extends Component {
}
}

Neo4jConfiguration.props = {
member: PropTypes.object.isRequired,
};

export default hoc.adminOnlyComponent(Neo4jConfiguration, 'Neo4j Configuration', false);
11 changes: 8 additions & 3 deletions src/components/db/LogsPane/LogsPane.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import _ from 'lodash';
import { Tab, Button, Icon, Form, Radio, Message, Checkbox } from 'semantic-ui-react';
import ReactTable from 'react-table';
import moment from 'moment';
import PropTypes from 'prop-types';

import sentry from '../../../api/sentry';
import kb from '../../../api/knowledgebase';
Expand Down Expand Up @@ -121,7 +122,7 @@ class LogViewer extends Component {
}

download() {
const promise = this.props.node.run(`
const promise = this.props.member.run(`
CALL apoc.log.stream("${this.props.file}") YIELD lineNo, line
RETURN line
ORDER BY lineNo ASC
Expand Down Expand Up @@ -190,7 +191,7 @@ class LogViewer extends Component {
if (Number.isNaN(n)) { n = 20; }

const params = { n, limit: neo4j.int(MAX_ROWS) };
const promise = this.props.node.run(query, params)
const promise = this.props.member.run(query, params)
.then(results => {
// Records are in reverse order to only get the last ones. Re-reverse them.
const data = neo4j.unpackResults(results, {
Expand Down Expand Up @@ -357,7 +358,7 @@ class LogsPane extends Component {
viewerFor(file) {
return (
<LogViewer key={file}
node={this.props.node}
node={this.props.member}
file={file} />
);
}
Expand Down Expand Up @@ -433,6 +434,10 @@ const notSupported = () => {
);
};

LogsPane.props = {
member: PropTypes.object.isRequired, // shape?
};

/**
* Logs viewing must be an admin only component, because logs can contain sensitive
* information. Read-only viewers (who might be able to see page cache details)
Expand Down
13 changes: 10 additions & 3 deletions src/components/db/PluginPane/PluginPane.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { Component } from 'react';
import CypherSurface from '../CypherSurface/CypherSurface';
import uuid from 'uuid';
import PropTypes from 'prop-types';

export default class PluginPane extends Component {
class PluginPane extends Component {
state = {
key: uuid.v4(),
};
Expand All @@ -12,9 +13,15 @@ export default class PluginPane extends Component {
<div className='PluginPane'>
<CypherSurface
key={this.state.key}
node={this.props.node}
node={this.props.member}
/>
</div>
)
}
}
}

PluginPane.props = {
member: PropTypes.object.isRequired, // shape?
};

export default PluginPane;
7 changes: 6 additions & 1 deletion src/components/db/SampleQueryPane/SampleQueryPane.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { Component } from 'react';
import uuid from 'uuid';
import SampleQueries from '../SampleQueries/SampleQueries';
import hoc from '../../higherOrderComponents';
import PropTypes from 'prop-types';

class SampleQueryPane extends Component {
state = {
Expand All @@ -13,11 +14,15 @@ class SampleQueryPane extends Component {
<div className='SampleQueryPane'>
<SampleQueries
key={this.state.key}
node={this.props.node}
node={this.props.member}
/>
</div>
)
}
}

SampleQueryPane.props = {
member: PropTypes.object.isRequired, // shape?
};

export default hoc.dbStatsOnlyComponent(SampleQueryPane, 'Query Performance', false);
Loading

0 comments on commit d086c5d

Please sign in to comment.