gitbay/Discovery/FeedEvent.swift
99 lines · 3813 bytes
1import Foundation
2
3/// One `feed` event. `data` varies by kind; unknown kinds and fields
4/// must render, not sink the feed.
5nonisolated struct FeedEvent: Decodable, Sendable, Hashable, Identifiable {
6 let id: Int64
7 let repo: String
8 let actor: String?
9 let kind: String
10 let data: Payload?
11 let createdAt: Date
12
13 enum CodingKeys: String, CodingKey {
14 case id, repo, actor, kind, data
15 case createdAt = "created_at"
16 }
17
18 nonisolated struct Payload: Decodable, Sendable, Hashable {
19 let number: Int64?
20 let sha: String?
21 let tag: String?
22 let title: String?
23 let job: String?
24 }
25
26 /// The same verb and reference phrasing as the web dashboard.
27 var phrase: String {
28 let n = data?.number.map(String.init) ?? ""
29 switch kind {
30 case "mr.created": return "opened merge request !\(n)"
31 case "mr.merged": return "merged !\(n)"
32 case "mr.commented": return "commented on !\(n)"
33 case "mr.closed": return "closed merge request !\(n)"
34 case "issue.created": return "opened issue #\(n)"
35 case "issue.closed": return "closed issue #\(n)"
36 case "issue.reopened": return "reopened issue #\(n)"
37 case "issue.commented": return "commented on #\(n)"
38 case "build.success": return "build success \(data?.job ?? "")"
39 case "build.failure": return "build failure \(data?.job ?? "")"
40 case "release.created": return "released \(data?.tag ?? "")"
41 case "push": return "pushed"
42 case let value where value.hasPrefix("issue."):
43 return "issue \(value.dropFirst("issue.".count)) #\(n)"
44 case let value where value.hasPrefix("mr."):
45 return "merge request \(value.dropFirst("mr.".count)) !\(n)"
46 case let value where value.hasPrefix("build."):
47 return "build \(value.dropFirst("build.".count)) \(data?.job ?? "")"
48 case let value where value.hasPrefix("repo."):
49 return "repository \(value.dropFirst("repo.".count))"
50 default: return kind
51 }
52 }
53
54 var displayActor: String {
55 guard let actor, !actor.isEmpty else { return "gitbay" }
56 return actor
57 }
58
59 var icon: (name: String, isPositive: Bool?) {
60 switch kind {
61 case "mr.created": ("arrow.triangle.merge", nil)
62 case "mr.merged": ("arrow.triangle.merge", true)
63 case "mr.closed": ("xmark.circle", false)
64 case "issue.created", "issue.open", "issue.reopened": ("smallcircle.filled.circle", nil)
65 case "issue.closed": ("checkmark.circle", true)
66 case "issue.commented": ("bubble.left", nil)
67 case "build.success": ("checkmark.circle.fill", true)
68 case "build.failure": ("xmark.circle.fill", false)
69 case "release.created": ("shippingbox", nil)
70 case "push": ("arrow.up.circle", nil)
71 default: ("circle", nil)
72 }
73 }
74
75 /// Where tapping the row goes, when the event names something the app
76 /// has a screen for.
77 var destination: FeedDestination? {
78 if kind == "release.created", let tag = data?.tag {
79 return .release(repo: repo, tag: tag)
80 }
81 guard let number = data?.number else {
82 return .repo(repo)
83 }
84 switch kind.split(separator: ".").first {
85 case "mr": return .mr(repo: repo, number: number)
86 case "issue": return .issue(repo: repo, number: number)
87 case "build": return .build(repo: repo, number: number)
88 default: return .repo(repo)
89 }
90 }
91}
92
93nonisolated enum FeedDestination: Hashable {
94 case repo(String)
95 case mr(repo: String, number: Int64)
96 case issue(repo: String, number: Int64)
97 case build(repo: String, number: Int64)
98 case release(repo: String, tag: String)
99}