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) {
|
||||
|
||||
app.get('/events', function (req, res) {
|
||||
eventDb.getEvents().then(function (events) {
|
||||
eventDiffer.addDiffs(events);
|
||||
res.json({events: events});
|
||||
});
|
||||
});
|
||||
@ -11,6 +13,7 @@ module.exports = function (app) {
|
||||
app.get('/events/:name', function (req, res) {
|
||||
eventDb.getEventsFilterByName(req.params.name).then(function (events) {
|
||||
if (events) {
|
||||
eventDiffer.addDiffs(events);
|
||||
res.json(events);
|
||||
} else {
|
||||
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",
|
||||
"body-parser": "1.10.1",
|
||||
"db-migrate": "0.7.1",
|
||||
"deep-diff": "^0.3.0",
|
||||
"errorhandler": "1.3.2",
|
||||
"express": "4.9.8",
|
||||
"express-validator": "2.8.0",
|
||||
|
@ -22,4 +22,16 @@
|
||||
code {
|
||||
word-wrap: break-word;
|
||||
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 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({
|
||||
propTypes: {
|
||||
event: React.PropTypes.object.isRequired
|
||||
@ -7,25 +21,64 @@ var LogEntry = React.createClass({
|
||||
|
||||
render: function() {
|
||||
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));
|
||||
delete localEventData.description;
|
||||
delete localEventData.name;
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
{d.getDate() + "." + d.getMonth() + "." + 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}}>
|
||||
<code className='JSON smalltext man'>{JSON.stringify(localEventData, null, 2)}</code>
|
||||
</td>
|
||||
<td>{this.props.event.createdBy}</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
var prettyPrinted = JSON.stringify(localEventData, null, 2);
|
||||
|
||||
return (<code className='JSON smalltext man'>{prettyPrinted}</code>)
|
||||
},
|
||||
|
||||
renderEventDiff: function() {
|
||||
if (!this.props.showFullEvents && this.props.event.diffs) {
|
||||
var changes = this.props.event.diffs.map(this.buildDiff);
|
||||
return (<code className='smalltext man'>{changes.length === 0 ? '(no changes)' : changes}</code>)
|
||||
} else {
|
||||
return this.renderFullEventData();
|
||||
}
|
||||
},
|
||||
|
||||
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;
|
@ -6,18 +6,32 @@ var LogEntryList = React.createClass({
|
||||
events: React.PropTypes.array.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showFullEvents: false
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
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 (
|
||||
<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'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>When</th>
|
||||
<th>Action</th>
|
||||
<th>Data</th>
|
||||
<th>
|
||||
Data
|
||||
</th>
|
||||
<th>Author</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -27,7 +41,12 @@ var LogEntryList = React.createClass({
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
toggleFullEvents: function(e) {
|
||||
this.setState({showFullEvents: !this.state.showFullEvents});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
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