mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
log views: show event diffs by default, toggle to show the full event.
This commit is contained in:
parent
72b48223e5
commit
12710a6d04
@ -1,9 +1,11 @@
|
|||||||
var eventDb = require('./eventDb');
|
var eventDb = require('./eventDb');
|
||||||
|
var eventDiffer = require('./eventDiffer');
|
||||||
|
|
||||||
module.exports = function (app) {
|
module.exports = function (app) {
|
||||||
|
|
||||||
app.get('/events', function (req, res) {
|
app.get('/events', function (req, res) {
|
||||||
eventDb.getEvents().then(function (events) {
|
eventDb.getEvents().then(function (events) {
|
||||||
|
eventDiffer.addDiffs(events);
|
||||||
res.json({events: events});
|
res.json({events: events});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -11,6 +13,7 @@ module.exports = function (app) {
|
|||||||
app.get('/events/:name', function (req, res) {
|
app.get('/events/:name', function (req, res) {
|
||||||
eventDb.getEventsFilterByName(req.params.name).then(function (events) {
|
eventDb.getEventsFilterByName(req.params.name).then(function (events) {
|
||||||
if (events) {
|
if (events) {
|
||||||
|
eventDiffer.addDiffs(events);
|
||||||
res.json(events);
|
res.json(events);
|
||||||
} else {
|
} else {
|
||||||
res.status(404).json({error: 'Could not find events'});
|
res.status(404).json({error: 'Could not find events'});
|
||||||
@ -18,5 +21,4 @@ module.exports = function (app) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
74
lib/eventDiffer.js
Normal file
74
lib/eventDiffer.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
var eventType = require('./eventType');
|
||||||
|
var diff = require('deep-diff').diff;
|
||||||
|
|
||||||
|
var strategyTypes = [
|
||||||
|
eventType.strategyCreated,
|
||||||
|
eventType.strategyDeleted
|
||||||
|
];
|
||||||
|
|
||||||
|
var featureTypes = [
|
||||||
|
eventType.featureCreated,
|
||||||
|
eventType.featureUpdated,
|
||||||
|
eventType.featureArchive,
|
||||||
|
eventType.featureRevive
|
||||||
|
];
|
||||||
|
|
||||||
|
function baseTypeFor(event) {
|
||||||
|
if (featureTypes.indexOf(event.type) !== -1) {
|
||||||
|
return 'features';
|
||||||
|
} else if (strategyTypes.indexOf(event.type) !== -1) {
|
||||||
|
return 'strategies';
|
||||||
|
} else {
|
||||||
|
throw new Error('unknown event type: ' + JSON.stringify(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupByBaseTypeAndName(events) {
|
||||||
|
var groups = {};
|
||||||
|
|
||||||
|
events.forEach(function (event) {
|
||||||
|
var baseType = baseTypeFor(event);
|
||||||
|
|
||||||
|
groups[baseType] = groups[baseType] || {};
|
||||||
|
groups[baseType][event.data.name] = groups[baseType][event.data.name] || [];
|
||||||
|
|
||||||
|
groups[baseType][event.data.name].push(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
function eachConsecutiveEvent(events, callback) {
|
||||||
|
var groups = groupByBaseTypeAndName(events);
|
||||||
|
|
||||||
|
Object.keys(groups).forEach(function (baseType) {
|
||||||
|
var group = groups[baseType];
|
||||||
|
|
||||||
|
Object.keys(group).forEach(function (name) {
|
||||||
|
var events = group[name];
|
||||||
|
var left, right, i, l;
|
||||||
|
for (i = 0, l = events.length; i < l; i++) {
|
||||||
|
left = events[i];
|
||||||
|
right = events[i + 1];
|
||||||
|
|
||||||
|
callback(left, right);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDiffs(events) {
|
||||||
|
eachConsecutiveEvent(events, function (left, right) {
|
||||||
|
if (right) {
|
||||||
|
left.diffs = diff(right.data, left.data);
|
||||||
|
left.diffs = left.diffs || [];
|
||||||
|
} else {
|
||||||
|
left.diffs = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
addDiffs: addDiffs
|
||||||
|
};
|
@ -36,6 +36,7 @@
|
|||||||
"bluebird": "2.6.2",
|
"bluebird": "2.6.2",
|
||||||
"body-parser": "1.10.1",
|
"body-parser": "1.10.1",
|
||||||
"db-migrate": "0.7.1",
|
"db-migrate": "0.7.1",
|
||||||
|
"deep-diff": "^0.3.0",
|
||||||
"errorhandler": "1.3.2",
|
"errorhandler": "1.3.2",
|
||||||
"express": "4.9.8",
|
"express": "4.9.8",
|
||||||
"express-validator": "2.8.0",
|
"express-validator": "2.8.0",
|
||||||
|
@ -22,4 +22,16 @@
|
|||||||
code {
|
code {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
code > .diff-N {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
code > .diff-D {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
code > .diff-A, .diff-E {
|
||||||
|
color: black;
|
||||||
}
|
}
|
@ -1,5 +1,19 @@
|
|||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
|
||||||
|
var DIFF_PREFIXES = {
|
||||||
|
A: ' ',
|
||||||
|
E: ' ',
|
||||||
|
D: '-',
|
||||||
|
N: '+'
|
||||||
|
}
|
||||||
|
|
||||||
|
var SPADEN_CLASS = {
|
||||||
|
A: 'blue', // array edited
|
||||||
|
E: 'blue', // edited
|
||||||
|
D: 'negative', // deleted
|
||||||
|
N: 'positive', // added
|
||||||
|
}
|
||||||
|
|
||||||
var LogEntry = React.createClass({
|
var LogEntry = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
event: React.PropTypes.object.isRequired
|
event: React.PropTypes.object.isRequired
|
||||||
@ -7,25 +21,64 @@ var LogEntry = React.createClass({
|
|||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var d = new Date(this.props.event.createdAt);
|
var d = new Date(this.props.event.createdAt);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{d.getDate() + "." + (d.getMonth() + 1) + "." + d.getFullYear()}<br />
|
||||||
|
kl. {d.getHours() + "." + d.getMinutes()}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<strong>{this.props.event.data.name}</strong><em>[{this.props.event.type}]</em>
|
||||||
|
</td>
|
||||||
|
<td style={{maxWidth: 300}}>
|
||||||
|
{this.renderEventDiff()}
|
||||||
|
</td>
|
||||||
|
<td>{this.props.event.createdBy}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderFullEventData: function() {
|
||||||
var localEventData = JSON.parse(JSON.stringify(this.props.event.data));
|
var localEventData = JSON.parse(JSON.stringify(this.props.event.data));
|
||||||
delete localEventData.description;
|
delete localEventData.description;
|
||||||
delete localEventData.name;
|
delete localEventData.name;
|
||||||
return (
|
|
||||||
<tr>
|
var prettyPrinted = JSON.stringify(localEventData, null, 2);
|
||||||
<td>
|
|
||||||
{d.getDate() + "." + d.getMonth() + "." + d.getFullYear()}<br />
|
return (<code className='JSON smalltext man'>{prettyPrinted}</code>)
|
||||||
kl. {d.getHours() + "." + d.getMinutes()}
|
},
|
||||||
</td>
|
|
||||||
<td>
|
renderEventDiff: function() {
|
||||||
<strong>{this.props.event.data.name}</strong><em>[{this.props.event.type}]</em>
|
if (!this.props.showFullEvents && this.props.event.diffs) {
|
||||||
</td>
|
var changes = this.props.event.diffs.map(this.buildDiff);
|
||||||
<td style={{maxWidth: 300}}>
|
return (<code className='smalltext man'>{changes.length === 0 ? '(no changes)' : changes}</code>)
|
||||||
<code className='JSON smalltext man'>{JSON.stringify(localEventData, null, 2)}</code>
|
} else {
|
||||||
</td>
|
return this.renderFullEventData();
|
||||||
<td>{this.props.event.createdBy}</td>
|
}
|
||||||
</tr>
|
},
|
||||||
);
|
|
||||||
|
buildDiff: function(diff, idx) {
|
||||||
|
var change;
|
||||||
|
var key = diff.path.join('.');
|
||||||
|
|
||||||
|
if (diff.lhs !== undefined && diff.rhs !== undefined) {
|
||||||
|
change = (
|
||||||
|
<div>
|
||||||
|
<div className={SPADEN_CLASS.D}>- {key}: {JSON.stringify(diff.lhs)}</div>
|
||||||
|
<div className={SPADEN_CLASS.N}>+ {key}: {JSON.stringify(diff.rhs)}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
var spadenClass = SPADEN_CLASS[diff.kind]
|
||||||
|
var prefix = DIFF_PREFIXES[diff.kind];
|
||||||
|
|
||||||
|
change = (<div className={spadenClass}>{prefix} {key}: {JSON.stringify(diff.rhs)}</div>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<div key={idx}>{change}</div>)
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = LogEntry;
|
module.exports = LogEntry;
|
@ -6,18 +6,32 @@ var LogEntryList = React.createClass({
|
|||||||
events: React.PropTypes.array.isRequired
|
events: React.PropTypes.array.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
showFullEvents: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var logEntryNodes = this.props.events.map(function(event) {
|
var logEntryNodes = this.props.events.map(function(event) {
|
||||||
return <LogEntry event={event} key={event.name} />;
|
return <LogEntry event={event} key={event.id} showFullEvents={this.state.showFullEvents} />;
|
||||||
});
|
}.bind(this));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=''>
|
<div>
|
||||||
|
<label className="prs fright-ht768 smalltext">
|
||||||
|
Show full events
|
||||||
|
<input type="checkbox" className="mlm" value={this.state.fullEvents} onChange={this.toggleFullEvents}></input>
|
||||||
|
</label>
|
||||||
|
|
||||||
<table className='outerborder zebra-striped'>
|
<table className='outerborder zebra-striped'>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>When</th>
|
<th>When</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
<th>Data</th>
|
<th>
|
||||||
|
Data
|
||||||
|
</th>
|
||||||
<th>Author</th>
|
<th>Author</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -27,7 +41,12 @@ var LogEntryList = React.createClass({
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleFullEvents: function(e) {
|
||||||
|
this.setState({showFullEvents: !this.state.showFullEvents});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = LogEntryList;
|
module.exports = LogEntryList;
|
97
test/eventDifferTest.js
Normal file
97
test/eventDifferTest.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
var eventDiffer = require('../lib/eventDiffer');
|
||||||
|
var eventType = require('../lib/eventType');
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
describe('eventDiffer', function () {
|
||||||
|
it('fails if events include an unknown event type', function () {
|
||||||
|
var events = [
|
||||||
|
{type: eventType.featureCreated, data: {}},
|
||||||
|
{type: 'unknown-type', data: {}}
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.throws(function () {
|
||||||
|
eventDiffer.addDiffs(events);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('diffs a feature-update event', function () {
|
||||||
|
var name = 'foo';
|
||||||
|
var desc = 'bar';
|
||||||
|
|
||||||
|
var events = [
|
||||||
|
{
|
||||||
|
type: eventType.featureUpdated,
|
||||||
|
data: {name: name, description: desc, strategy: 'default', enabled: true, parameters: {value: 2 }}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: eventType.featureCreated,
|
||||||
|
data: {name: name, description: desc, strategy: 'default', enabled: false, parameters: {value: 1}}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
eventDiffer.addDiffs(events);
|
||||||
|
|
||||||
|
assert.deepEqual(events[0].diffs, [
|
||||||
|
{kind: 'E', path: ["enabled"], lhs: false, rhs: true},
|
||||||
|
{kind: 'E', path: ["parameters", "value"], lhs: 1, rhs: 2}
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.strictEqual(events[1].diffs, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('diffs only against features with the same name', function () {
|
||||||
|
var events = [
|
||||||
|
{
|
||||||
|
type: eventType.featureUpdated,
|
||||||
|
data: {name: 'bar', description: 'desc', strategy: 'default', enabled: true, parameters: {}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: eventType.featureUpdated,
|
||||||
|
data: {name: 'foo', description: 'desc', strategy: 'default', enabled: false, parameters: {}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: eventType.featureCreated,
|
||||||
|
data: {name: 'bar', description: 'desc', strategy: 'default', enabled: false, parameters: {}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: eventType.featureCreated,
|
||||||
|
data: {name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {}}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
eventDiffer.addDiffs(events);
|
||||||
|
|
||||||
|
assert.strictEqual(events[0].diffs[0].rhs, true);
|
||||||
|
assert.strictEqual(events[1].diffs[0].rhs, false);
|
||||||
|
assert.strictEqual(events[2].diffs, null);
|
||||||
|
assert.strictEqual(events[3].diffs, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets an empty array of diffs if nothing was changed', function () {
|
||||||
|
var events = [
|
||||||
|
{
|
||||||
|
type: eventType.featureUpdated,
|
||||||
|
data: {name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: eventType.featureCreated,
|
||||||
|
data: {name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {}}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
eventDiffer.addDiffs(events);
|
||||||
|
assert.deepEqual(events[0].diffs, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets diffs to null if there was nothing to diff against', function () {
|
||||||
|
var events = [
|
||||||
|
{
|
||||||
|
type: eventType.featureUpdated,
|
||||||
|
data: {name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {}}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
eventDiffer.addDiffs(events);
|
||||||
|
assert.strictEqual(events[0].diffs, null);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user