Skip to content
This repository has been archived by the owner on Aug 11, 2024. It is now read-only.

Commit

Permalink
Log key activities in SQLite (#1067)
Browse files Browse the repository at this point in the history
Trying not to over engineer anything here.

- Create a new `activity` SQLite table
- Log events we may be interested in responding to / surfacing:
- Swipe choices in Deck view (step towards
#1023)
    - Follow / unfollow user actions
    - Index complete + how many changes

As mentioned above, this is a step towards offering more sophisticated
behaviour in the Deck view over multiple sessions but it could also let
us offer an "activity" view in the app where you can see updates like
"`@gordon` published 4 notes".


https://github.com/subconsciousnetwork/subconscious/assets/5009316/c05dbb91-d3fa-47b1-bdff-60081f8761a0
  • Loading branch information
bfollington authored Jan 16, 2024
1 parent 4b93044 commit ad2681c
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 19 deletions.
60 changes: 54 additions & 6 deletions xcode/Subconscious/Shared/Components/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ enum AppAction: Hashable {
case failMergeEntry(parent: Slashlink, child: Slashlink, error: String)
case failUpdateAudience(address: Slashlink, audience: Audience, error: String)

case succeedLogActivity
case failLogActivity(_ error: String)

case setSelectedAppTab(AppTab)
case requestNotebookRoot
case requestProfileRoot
Expand Down Expand Up @@ -1217,14 +1220,15 @@ struct AppModel: ModelProtocol {
state: state,
environment: environment
)
case .succeedMoveEntry(from: let from, to: let to):
return Update(state: state)
case .succeedMergeEntry(parent: let parent, child: let child):
case .succeedMoveEntry, .succeedMergeEntry, .succeedLogActivity, .succeedUpdateAudience:
return Update(state: state)
case .succeedSaveEntry(address: let address, modified: let modified):
return Update(state: state)
case .succeedUpdateAudience(_):
return Update(state: state)
return succeedSaveEntry(
state: state,
environment: environment,
address: address,
modified: modified
)
case let .saveEntry(entry):
return saveEntry(
state: state,
Expand Down Expand Up @@ -1309,6 +1313,13 @@ struct AppModel: ModelProtocol {
),
environment: environment
)
case let .failLogActivity(error):
logger.warning(
"""
Failed to log activity: \(error)
"""
)
return Update(state: state)
}
}

Expand Down Expand Up @@ -2967,6 +2978,43 @@ struct AppModel: ModelProtocol {
return Update(state: state, fx: fx)
}

struct SucceedSaveEntryActivityEvent: Codable {
public static let event = "save_entry"
public let address: String
}

static func succeedSaveEntry(
state: Self,
environment: AppEnvironment,
address: Slashlink,
modified: Date
) -> Update<Self> {
let fx: Fx<AppAction> = Future.detached {
try environment.database.writeActivity(
event: ActivityEvent(
category: .system,
event: SucceedSaveEntryActivityEvent.event,
message: "",
metadata: SucceedSaveEntryActivityEvent(address: address.description)
)
)

return .succeedLogActivity
}
.recover { error in
.failLogActivity(error.localizedDescription)
}
.eraseToAnyPublisher()

logger.log(
"Saved entry",
metadata: [
"address": address.description
]
)
return Update(state: state, fx: fx)
}

static func mergeEntry(
state: Self,
environment: AppEnvironment,
Expand Down
28 changes: 23 additions & 5 deletions xcode/Subconscious/Shared/Components/Deck/DeckView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -586,11 +586,29 @@ struct DeckModel: ModelProtocol {

return Update(state: state)
}


func shuffleInBacklinks(_ backlinks: Set<EntryStub>) -> Update<Self> {
struct ChooseCardActivityEvent: Codable {
public static let event: String = "choose_card"

let address: String
}

func shuffleInBacklinks(message: String, address: Slashlink, backlinks: Set<EntryStub>) -> Update<Self> {
let fx: Fx<DeckAction> = Future.detached {
let us = try await environment.noosphere.identity()

try environment.database.writeActivity(
event: ActivityEvent(
category: .deck,
event: ChooseCardActivityEvent.event,
message: message,
metadata: ChooseCardActivityEvent(
address: address.description
)
)
)

// Filter to valid backlinks
let backlinks = backlinks
.filter({ backlink in !isSeen(entry: backlink) })
Expand Down Expand Up @@ -629,10 +647,10 @@ struct DeckModel: ModelProtocol {
state.feedback.impactOccurred()

switch card.card {
case let .entry(_, _, related):
return shuffleInBacklinks(related)
case let .prompt(_, _, _, related):
return shuffleInBacklinks(related)
case let .entry(entry, _, related):
return shuffleInBacklinks(message: "", address: entry.address, backlinks: related)
case let .prompt(message, entry, _, related):
return shuffleInBacklinks(message: message, address: entry.address, backlinks: related)
case .action(let msg):
logger.log("Action: \(msg)")
return update(
Expand Down
22 changes: 22 additions & 0 deletions xcode/Subconscious/Shared/Models/ActivityEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// ActivityEvent.swift
// Subconscious (iOS)
//
// Created by Ben Follington on 10/1/2024.
//

import Foundation

struct ActivityEvent<Metadata: Codable>: Codable {
let category: ActivityEventCategory
let event: String
let message: String
let metadata: Metadata?
}

enum ActivityEventCategory: String, Codable {
case system = "system"
case deck = "deck"
case note = "note"
case addressBook = "addressBook"
}
44 changes: 44 additions & 0 deletions xcode/Subconscious/Shared/Services/AddressBookService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,13 @@ actor AddressBookService {
.eraseToAnyPublisher()
}

struct FollowUserActivityEvent: Codable {
public static let event: String = "follow_user"

let did: String
let petname: String
}

/// Associates the passed DID with the passed petname within the sphere,
/// saves the changes and updates the database.
func followUser(
Expand All @@ -344,6 +351,23 @@ actor AddressBookService {

try await noosphere.setPetname(did: did, petname: petname)
let _ = try await noosphere.save()

try database.writeActivity(
event: ActivityEvent(
category: .addressBook,
event: FollowUserActivityEvent.event,
message: "Followed \(petname.markup)",
metadata: FollowUserActivityEvent(
did: did.did.description,
petname: petname.description
)
)
)
}

struct AddressBookActivityEventMetadata: Codable {
let did: String
let petname: String
}

func resolutionStatus(petname: Petname) async throws -> ResolutionStatus {
Expand Down Expand Up @@ -416,12 +440,32 @@ actor AddressBookService {
.eraseToAnyPublisher()
}

struct UnfollowUserActivityEvent: Codable {
public static let event: String = "unfollow_user"

let did: String
let petname: String
}

/// Disassociates the passed Petname from any DID within the sphere,
/// saves the changes and updates the database.
func unfollowUser(petname: Petname) async throws -> Did {
let did = try await self.noosphere.getPetname(petname: petname)
try await self.addressBook.unsetPetname(petname: petname)
let _ = try await self.noosphere.save()

try database.writeActivity(
event: ActivityEvent(
category: .addressBook,
event: UnfollowUserActivityEvent.event,
message: "Unfollowed \(petname.markup)",
metadata: UnfollowUserActivityEvent(
did: did.description,
petname: petname.description
)
)
)

return did
}

Expand Down
37 changes: 31 additions & 6 deletions xcode/Subconscious/Shared/Services/DataService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ actor DataService {
/// - If sphere has not been indexed, we get everything, and update
func indexPeer(
petname: Petname
) async throws -> PeerRecord {
) async throws -> (Int, PeerRecord) {
let sphere = try await noosphere.traverse(petname: petname)
let identity = try await sphere.identity()
logger.log(
Expand Down Expand Up @@ -147,6 +147,7 @@ actor DataService {

let savepoint = "index_peer"
try database.savepoint(savepoint)

do {
for change in changes {
let slashlink = Slashlink(slug: change)
Expand Down Expand Up @@ -212,13 +213,23 @@ actor DataService {
)
throw error
}
return PeerRecord(
petname: petname,
identity: identity,
since: version
return (
changes.count,
PeerRecord(
petname: petname,
identity: identity,
since: version
)
)
}

struct SucceedIndexPeerActivityEvent: Codable {
public static let event = "succeed_index_peer"
let did: String
let petname: String
let changes: Int
}

func indexPeers(petnames: [Petname]) async -> [PeerIndexResult] {
var results: [PeerIndexResult] = []

Expand All @@ -231,11 +242,25 @@ actor DataService {
)

do {
let peer = try await self.indexPeer(
let (changeCount, peer) = try await self.indexPeer(
petname: petname
)

results.append(.success(peer))

try database.writeActivity(
event: ActivityEvent(
category: .system,
event: SucceedIndexPeerActivityEvent.event,
message: "Found \(changeCount) updates from \(petname.markup)",
metadata: SucceedIndexPeerActivityEvent(
did: peer.identity.description,
petname: petname.description,
changes: changeCount
)
)
)

// Give other tasks a chance to use noosphere, indexing many peers
// can take a long time and potentially block user actions
await Task.yield()
Expand Down
Loading

0 comments on commit ad2681c

Please sign in to comment.