Skip to content

Commit d8e010f

Browse files
authored
Refactor activity log types (#127)
* prototype activity log layout * fix types * refactor types * refactor names * refactor variable names * fix sorting bug * docs
1 parent 0aaa890 commit d8e010f

File tree

7 files changed

+73
-56
lines changed

7 files changed

+73
-56
lines changed

main/activity-log.js

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,46 @@
11
'use strict'
22

3-
/** @typedef {import('./typings').ActivityEvent} ActivityEvent */
4-
/** @typedef {import('./typings').ActivityEntry} ActivityEntry */
3+
/** @typedef {import('./typings').Activity} Activity */
4+
/** @typedef {import('./typings').RecordActivityOptions} RecordActivityOptions */
55

66
const Store = require('electron-store')
7+
const crypto = require('node:crypto')
8+
79
const activityLogStore = new Store({
810
name: 'activity-log'
911
})
1012

1113
class ActivityLog {
1214
#entries
13-
#lastId
1415

1516
constructor () {
1617
this.#entries = loadStoredEntries()
17-
this.#lastId = Number(this.#entries.at(-1)?.id ?? 0)
1818
}
1919

2020
/**
21-
* @param {ActivityEvent} args
22-
* @returns {ActivityEntry}
21+
* @param {RecordActivityOptions} args
22+
* @returns {Activity}
2323
*/
24-
recordEvent ({ source, type, message }) {
25-
const nextId = ++this.#lastId
26-
/** @type {ActivityEntry} */
27-
const entry = {
28-
id: String(nextId),
24+
recordActivity ({ source, type, message }) {
25+
/** @type {Activity} */
26+
const activity = {
27+
id: crypto.randomUUID(),
2928
timestamp: Date.now(),
3029
source,
3130
type,
3231
message
3332
}
3433
// Freeze the data to prevent ActivityLog users from accidentally changing our store
35-
Object.freeze(entry)
34+
Object.freeze(activity)
3635

37-
this.#entries.push(entry)
36+
this.#entries.push(activity)
3837

3938
if (this.#entries.length > 100) {
40-
// Delete the oldest entry to keep ActivityLog at constant size
39+
// Delete the oldest activity to keep ActivityLog at constant size
4140
this.#entries.shift()
4241
}
4342
this.#save()
44-
return entry
43+
return activity
4544
}
4645

4746
getAllEntries () {
@@ -56,12 +55,12 @@ class ActivityLog {
5655
}
5756

5857
#save () {
59-
activityLogStore.set('events', this.#entries)
58+
activityLogStore.set('activities', this.#entries)
6059
}
6160
}
6261

6362
/**
64-
* @returns {ActivityEntry[]}
63+
* @returns {Activity[]}
6564
*/
6665
function loadStoredEntries () {
6766
// A workaround to fix false TypeScript errors

main/index.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ const { setupAppMenu } = require('./app-menu')
1515
const { ActivityLog } = require('./activity-log')
1616
const { ipcMain } = require('electron/main')
1717

18-
/** @typedef {import('./typings').ActivityEvent} ActivityEvent */
19-
/** @typedef {import('./typings').ActivityEntry} ActivityEntry */
18+
/** @typedef {import('./typings').Activity} Activity */
19+
/** @typedef {import('./typings').RecordActivityOptions} RecordActivityOptions */
2020

2121
const inTest = (process.env.NODE_ENV === 'test')
2222
const isDev = !app.isPackaged && !inTest
@@ -94,18 +94,18 @@ const activityLog = new ActivityLog()
9494
let isActivityStreamFlowing = false
9595

9696
/**
97-
* @param {ActivityEvent} event
97+
* @param {RecordActivityOptions} opts
9898
*/
99-
function recordActivity (event) {
100-
const entry = activityLog.recordEvent(event)
101-
if (isActivityStreamFlowing) emitActivity(entry)
99+
function recordActivity (opts) {
100+
const activity = activityLog.recordActivity(opts)
101+
if (isActivityStreamFlowing) emitActivity(activity)
102102
}
103103

104104
/**
105-
* @param {ActivityEntry} entry
105+
* @param {Activity} activity
106106
*/
107-
function emitActivity (entry) {
108-
ipcMain.emit(ipcMainEvents.ACTIVITY_LOGGED, entry)
107+
function emitActivity (activity) {
108+
ipcMain.emit(ipcMainEvents.ACTIVITY_LOGGED, activity)
109109
}
110110

111111
function resumeActivityStream () {

main/preload.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ contextBridge.exposeInMainWorld('electron', {
66
resumeActivityStream: () => ipcRenderer.invoke('station:resumeActivityStream'),
77

88
/**
9-
* @param {(activityEntry: import('./typings').ActivityEntry) => void} callback
9+
* @param {(Activity: import('./typings').Activity) => void} callback
1010
*/
1111
onActivityLogged (callback) {
12-
ipcRenderer.on('station:activity-logged', (_event, entry) => callback(entry))
12+
ipcRenderer.on('station:activity-logged', (_event, activity) => callback(activity))
1313
},
1414

1515
saturnNode: {

main/test/activity-log.test.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,47 @@ const assert = require('assert').strict
44
const { ActivityLog } = require('../activity-log')
55
const { assertTimestampIsCloseToNow, pickProps } = require('./test-helpers')
66

7-
/** @typedef {import('../typings').ActivityEvent} ActivityEvent */
7+
/** @typedef {import('../typings').RecordActivityOptions} RecordActivityOptions */
88

99
describe('ActivityLog', function () {
1010
beforeEach(function () { return ActivityLog.reset() })
1111

12-
it('record events and assign them timestamp and id ', function () {
12+
it('record activities and assign them timestamp and id ', function () {
1313
const activityLog = new ActivityLog()
14-
const entryCreated = activityLog.recordEvent(givenActivity({
14+
const activityCreated = activityLog.recordActivity(givenActivity({
1515
source: 'Station',
1616
type: 'info',
1717
message: 'Hello world!'
1818
}))
1919

2020
assert.strictEqual(activityLog.getAllEntries().length, 1)
21-
assert.deepStrictEqual(entryCreated, activityLog.getAllEntries()[0])
21+
assert.deepStrictEqual(activityCreated, activityLog.getAllEntries()[0])
2222

23-
const { timestamp, ...entry } = activityLog.getAllEntries()[0]
24-
assert.deepStrictEqual(entry, {
23+
const { timestamp, ...activity } = activityLog.getAllEntries()[0]
24+
assert.deepStrictEqual(activity, {
2525
id: '1',
2626
source: 'Station',
2727
type: 'info',
2828
message: 'Hello world!'
2929
})
3030

31-
assertTimestampIsCloseToNow(timestamp, 'event.timestamp')
31+
assertTimestampIsCloseToNow(timestamp, 'activity.timestamp')
3232
})
3333

3434
it('assigns unique ids', function () {
3535
const activityLog = new ActivityLog()
36-
activityLog.recordEvent(givenActivity({ message: 'one' }))
37-
activityLog.recordEvent(givenActivity({ message: 'two' }))
36+
activityLog.recordActivity(givenActivity({ message: 'one' }))
37+
activityLog.recordActivity(givenActivity({ message: 'two' }))
3838
assert.deepStrictEqual(activityLog.getAllEntries().map(it => pickProps(it, 'id', 'message')), [
3939
{ id: '1', message: 'one' },
4040
{ id: '2', message: 'two' }
4141
])
4242
})
4343

44-
it('preserves events across restarts', function () {
45-
new ActivityLog().recordEvent(givenActivity({ message: 'first run' }))
44+
it('preserves activities across restarts', function () {
45+
new ActivityLog().recordActivity(givenActivity({ message: 'first run' }))
4646
const activityLog = new ActivityLog()
47-
activityLog.recordEvent(givenActivity({ message: 'second run' }))
47+
activityLog.recordActivity(givenActivity({ message: 'second run' }))
4848
assert.deepStrictEqual(activityLog.getAllEntries().map(it => pickProps(it, 'id', 'message')), [
4949
{ id: '1', message: 'first run' },
5050
{ id: '2', message: 'second run' }
@@ -56,19 +56,19 @@ describe('ActivityLog', function () {
5656

5757
const log = new ActivityLog()
5858
for (let i = 0; i < 110; i++) {
59-
log.recordEvent(givenActivity({ message: `event ${i}` }))
59+
log.recordActivity(givenActivity({ message: `activity ${i}` }))
6060
}
6161
const entries = log.getAllEntries()
6262
assert.deepStrictEqual(
6363
[entries.at(0)?.message, entries.at(-1)?.message],
64-
['event 10', 'event 109']
64+
['activity 10', 'activity 109']
6565
)
6666
})
6767
})
6868

6969
/**
70-
* @param {Partial<ActivityEvent>} [props]
71-
* @returns {ActivityEvent}
70+
* @param {Partial<RecordActivityOptions>} [props]
71+
* @returns {RecordActivityOptions}
7272
*/
7373
function givenActivity (props) {
7474
return {

main/typings.d.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
export type ActivitySource = 'Station' | 'Saturn';
2-
export type ActivityEventType = 'info' | 'error';
2+
export type ActivityType = 'info' | 'error';
33

4-
export interface ActivityEvent {
5-
type: ActivityEventType;
4+
export interface Activity {
5+
id: string;
6+
timestamp: number;
7+
type: ActivityType;
68
source: ActivitySource;
79
message: string;
810
}
911

10-
export interface ActivityEntry extends ActivityEvent {
11-
id: string;
12-
timestamp: number;
12+
export interface RecordActivityOptions {
13+
type: ActivityType;
14+
source: ActivitySource;
15+
message: string;
1316
}
1417

1518
export interface Context {
16-
recordActivity(event: ActivityEvent): void;
19+
recordActivity(activity: RecordActivityOptions): void;
1720
resumeActivityStream(): void;
1821

1922
showUI: () => void

renderer/src/main.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'
33
import { BrowserRouter } from 'react-router-dom'
44
import App from './App'
55
import './index.css'
6+
import { Activity } from '../../main/typings'
67

78
ReactDOM.createRoot(
89
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -15,8 +16,22 @@ ReactDOM.createRoot(
1516
</React.StrictMode>
1617
)
1718

18-
window.electron.onActivityLogged(entry => {
19-
console.log('[ACTIVITY] %j', entry)
19+
const activities: Activity[] = []
20+
21+
window.electron.onActivityLogged(activity => {
22+
activities.push(activity)
23+
// In case two activities were recorded in the same millisecond, fall back to
24+
// sorting by their IDs, which are guaranteed to be unique and therefore
25+
// provide a stable sorting.
26+
activities.sort((a, b) => {
27+
return a.timestamp !== b.timestamp
28+
? b.timestamp - a.timestamp
29+
: a.id.localeCompare(b.id)
30+
})
31+
if (activities.length > 100) activities.shift()
32+
33+
console.log('[ACTIVITY] %j', activity)
34+
console.log('[ACTIVITIES]', activities)
2035
})
2136

2237
window.electron.resumeActivityStream().then(() => {

renderer/src/typings.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { ActivityEntry } from '../main/typings'
1+
import { Activity } from '../main/typings'
22

33
export declare global {
44
interface Window {
55
electron: {
6-
resumeActivityStream(): Promise<ActivityEntry[]>,
7-
onActivityLogged(callback: (activityEntry: ActivityEntry) => void),
6+
resumeActivityStream(): Promise<Activity[]>,
7+
onActivityLogged(callback: (activity: Activity) => void),
88

99
saturnNode: {
1010
start:() => Promise<void>,

0 commit comments

Comments
 (0)