refactor(history): share local recents projection
This commit is contained in:
1 parent
a728b48df2
commit
78070dcdcd
9 files changed
+98
-42
No files matched your search
@@ -0,0 +1,50 @@
|
||||
import Foundation
|
||||
import TermindApplication
|
||||
import TermindDomain
|
||||
import Testing
|
||||
|
||||
import struct TermindDomain.Host
|
||||
|
||||
@Suite @MainActor
|
||||
struct RecentConnectionHostTests {
|
||||
@Test func projectionKeepsTheNewestLocalEventForEachSavedHost() {
|
||||
let localID = UUID()
|
||||
let first = Host(name: "First", host: "first.example")
|
||||
let second = Host(name: "Second", host: "second.example")
|
||||
let removedID = UUID()
|
||||
let firstOlder = ConnectionHistoryEntry(
|
||||
hostID: first.id, kind: .ssh,
|
||||
connectedAt: Date(timeIntervalSince1970: 10), sourceDeviceID: localID)
|
||||
let secondLatest = ConnectionHistoryEntry(
|
||||
hostID: second.id, kind: .telnet,
|
||||
connectedAt: Date(timeIntervalSince1970: 30), sourceDeviceID: localID)
|
||||
let firstLatest = ConnectionHistoryEntry(
|
||||
hostID: first.id, kind: .sftp,
|
||||
connectedAt: Date(timeIntervalSince1970: 20), sourceDeviceID: localID)
|
||||
let history = ConnectionHistoryModel(
|
||||
entries: [
|
||||
firstOlder,
|
||||
ConnectionHistoryEntry(
|
||||
hostID: first.id, kind: .ssh,
|
||||
connectedAt: Date(timeIntervalSince1970: 50), sourceDeviceID: UUID()),
|
||||
secondLatest,
|
||||
ConnectionHistoryEntry(
|
||||
hostID: removedID, kind: .ssh,
|
||||
connectedAt: Date(timeIntervalSince1970: 40), sourceDeviceID: localID),
|
||||
firstLatest,
|
||||
ConnectionHistoryEntry(
|
||||
hostID: second.id, kind: .ssh,
|
||||
connectedAt: Date(timeIntervalSince1970: 60)),
|
||||
],
|
||||
sourceDeviceID: localID)
|
||||
let library = HostLibraryModel(hosts: [first, second])
|
||||
|
||||
let all = history.recentLocalHosts(in: library, limit: 5)
|
||||
#expect(all.map(\.id) == [second.id, first.id])
|
||||
#expect(all.map(\.entry.id) == [secondLatest.id, firstLatest.id])
|
||||
#expect(all.map(\.host.name) == ["Second", "First"])
|
||||
#expect(history.recentLocalHosts(in: library, limit: 1).map(\.id) == [second.id])
|
||||
#expect(history.recentLocalHosts(in: library, limit: 0).isEmpty)
|
||||
#expect(history.recentLocalHosts(in: library, limit: -1).isEmpty)
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,13 @@ import TermindDomain
|
||||
|
||||
import struct TermindDomain.Host
|
||||
|
||||
public struct RecentConnectionHost: Identifiable {
|
||||
public let entry: ConnectionHistoryEntry
|
||||
public let host: Host
|
||||
|
||||
public var id: Host.ID { host.id }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
public final class ConnectionHistoryModel {
|
||||
@@ -55,6 +62,20 @@ public final class ConnectionHistoryModel {
|
||||
entries().filter(isLocal)
|
||||
}
|
||||
|
||||
/// The latest local connection for each saved Host, in connection order.
|
||||
public func recentLocalHosts(in library: HostLibraryModel, limit: Int) -> [RecentConnectionHost] {
|
||||
guard limit > 0 else { return [] }
|
||||
|
||||
var seen = Set<Host.ID>()
|
||||
var hosts: [RecentConnectionHost] = []
|
||||
for entry in localEntries() {
|
||||
guard let host = library.host(entry.hostID), seen.insert(entry.hostID).inserted else { continue }
|
||||
hosts.append(RecentConnectionHost(entry: entry, host: host))
|
||||
if hosts.count == limit { break }
|
||||
}
|
||||
return hosts
|
||||
}
|
||||
|
||||
public func mostRecentDate(for hostID: Host.ID) -> Date? {
|
||||
entries().first { $0.hostID == hostID }?.connectedAt
|
||||
}
|
||||
|
||||
@@ -49,28 +49,20 @@ enum MobileConnectionHistoryActionPolicy {
|
||||
@MainActor
|
||||
enum MobileConnectionHistory {
|
||||
static func recentHostRows(
|
||||
entries: [ConnectionHistoryEntry],
|
||||
history: ConnectionHistoryModel,
|
||||
library: HostLibraryModel,
|
||||
limit: Int
|
||||
) -> [MobileRecentHostRow] {
|
||||
guard limit > 0 else { return [] }
|
||||
|
||||
var seen = Set<Host.ID>()
|
||||
var rows: [MobileRecentHostRow] = []
|
||||
for entry in entries {
|
||||
guard let host = library.host(entry.hostID) else { continue }
|
||||
guard seen.insert(host.id).inserted else { continue }
|
||||
history.recentLocalHosts(in: library, limit: limit).map { recent in
|
||||
let host = recent.host
|
||||
let preferredKind = preferredKind(for: host)
|
||||
rows.append(
|
||||
MobileRecentHostRow(
|
||||
entry: entry,
|
||||
host: host,
|
||||
loginAddress: library.preferredTerminalLoginAddress(for: host),
|
||||
preferredKind: preferredKind
|
||||
))
|
||||
if rows.count == limit { break }
|
||||
return MobileRecentHostRow(
|
||||
entry: recent.entry,
|
||||
host: host,
|
||||
loginAddress: library.preferredTerminalLoginAddress(for: host),
|
||||
preferredKind: preferredKind
|
||||
)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
static func eventRows(
|
||||
|
||||
@@ -187,7 +187,7 @@ struct MobileWorkspaceListView: View {
|
||||
var historyRows: [MobileRecentHostRow] {
|
||||
_ = app.connectionHistory.revision
|
||||
return MobileConnectionHistory.recentHostRows(
|
||||
entries: app.connectionHistory.localEntries(),
|
||||
history: app.connectionHistory,
|
||||
library: app.library,
|
||||
limit: Self.historyLimit
|
||||
)
|
||||
|
||||
@@ -77,7 +77,7 @@ enum MobileHomeScreenQuickActions {
|
||||
|
||||
static func refresh(app: MobileAppModel) {
|
||||
let rows = MobileConnectionHistory.recentHostRows(
|
||||
entries: app.connectionHistory.localEntries(),
|
||||
history: app.connectionHistory,
|
||||
library: app.library,
|
||||
limit: ConnectionHistoryModel.limit
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@ struct MobileHomeScreenQuickActionTests {
|
||||
kind: .ssh, for: UUID(), at: Date(timeIntervalSince1970: 70))
|
||||
|
||||
let rows = MobileConnectionHistory.recentHostRows(
|
||||
entries: app.connectionHistory.localEntries(),
|
||||
history: app.connectionHistory,
|
||||
library: app.library,
|
||||
limit: ConnectionHistoryModel.limit
|
||||
)
|
||||
|
||||
@@ -980,17 +980,6 @@ final class AppModel {
|
||||
connectionHistory.clearAll()
|
||||
}
|
||||
|
||||
/// Saved Hosts ordered by their latest connection event, with each Host
|
||||
/// appearing only once.
|
||||
func recentHosts(limit: Int = 5, localOnly: Bool = false) -> [Host] {
|
||||
var seen = Set<Host.ID>()
|
||||
let entries = localOnly ? connectionHistory.localEntries() : connectionHistory.entries()
|
||||
return entries.compactMap { entry in
|
||||
guard seen.insert(entry.hostID).inserted else { return nil }
|
||||
return hosts.host(entry.hostID)
|
||||
}.prefix(limit).map { $0 }
|
||||
}
|
||||
|
||||
func lastConnectedAt(for hostID: Host.ID) -> Date? {
|
||||
connectionHistory.mostRecentDate(for: hostID)
|
||||
}
|
||||
|
||||
@@ -989,11 +989,12 @@ struct WelcomeView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
let hosts = app.recentHosts(limit: max(0, 6 - templates.count), localOnly: true)
|
||||
let hosts = app.connectionHistory.recentLocalHosts(
|
||||
in: app.hostLibrary, limit: max(0, 6 - templates.count))
|
||||
if !hosts.isEmpty {
|
||||
section(AppStrings.Workspace.recents) {
|
||||
ForEach(hosts) { host in
|
||||
WelcomeHostRow(host: host)
|
||||
ForEach(hosts) { recent in
|
||||
WelcomeHostRow(host: recent.host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ struct ConnectionHistoryTests {
|
||||
|
||||
#expect(history.entries().map(\.hostID) == [first.id, second.id, first.id])
|
||||
#expect(history.entries().map(\.kind) == [.sftp, .sftp, .ssh])
|
||||
#expect(app.recentHosts().map(\.id) == [first.id, second.id])
|
||||
#expect(history.recentLocalHosts(in: app.hostLibrary, limit: 5).map(\.id) == [first.id, second.id])
|
||||
#expect(app.lastConnectedAt(for: first.id) != nil)
|
||||
}
|
||||
|
||||
@@ -270,8 +270,8 @@ struct ConnectionHistoryTests {
|
||||
let app = AppModel(
|
||||
hosts: HostLibraryModel(hosts: [local, remote]), connectionHistory: history)
|
||||
|
||||
#expect(app.recentHosts(localOnly: true).map(\.id) == [local.id])
|
||||
#expect(app.recentHosts(localOnly: false).map(\.id) == [remote.id, local.id])
|
||||
#expect(history.recentLocalHosts(in: app.hostLibrary, limit: 5).map(\.id) == [local.id])
|
||||
#expect(history.entries().map(\.hostID) == [remote.id, local.id])
|
||||
#expect(app.lastConnectedLocallyAt(for: local.id) == Date(timeIntervalSince1970: 10))
|
||||
#expect(app.lastConnectedLocallyAt(for: remote.id) == nil)
|
||||
}
|
||||
@@ -280,10 +280,13 @@ struct ConnectionHistoryTests {
|
||||
func clearAllHistoryForHostKeepsHostAndOtherHostHistory() {
|
||||
let host = Host(name: "Host", host: "host.example")
|
||||
let other = Host(name: "Other", host: "other.example")
|
||||
let connections = ConnectionHistoryModel(entries: [
|
||||
ConnectionHistoryEntry(hostID: host.id, kind: .ssh),
|
||||
ConnectionHistoryEntry(hostID: other.id, kind: .ssh),
|
||||
])
|
||||
let localID = UUID()
|
||||
let connections = ConnectionHistoryModel(
|
||||
entries: [
|
||||
ConnectionHistoryEntry(hostID: host.id, kind: .ssh, sourceDeviceID: localID),
|
||||
ConnectionHistoryEntry(hostID: other.id, kind: .ssh, sourceDeviceID: localID),
|
||||
],
|
||||
sourceDeviceID: localID)
|
||||
let commands = CommandHistoryModel(entries: [
|
||||
CommandHistoryEntry(hostID: host.id, command: "uptime")
|
||||
])
|
||||
@@ -303,6 +306,6 @@ struct ConnectionHistoryTests {
|
||||
#expect(commands.entries(for: host.id).isEmpty)
|
||||
#expect(files.entries(for: host.id).isEmpty)
|
||||
#expect(connections.entries(for: other.id).count == 1)
|
||||
#expect(app.recentHosts().map(\.id) == [other.id])
|
||||
#expect(connections.recentLocalHosts(in: app.hostLibrary, limit: 5).map(\.id) == [other.id])
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user