stuff
This commit is contained in:
@@ -51,6 +51,14 @@
|
||||
"messageHeadline": "Split prompt packing from worker execution"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestCommit",
|
||||
"commit": {
|
||||
"committedDate": "2026-05-01T03:12:00Z",
|
||||
"abbreviatedOid": "b71c44e",
|
||||
"messageHeadline": "Add worker context envelope validation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T03:20:00Z",
|
||||
@@ -61,6 +69,16 @@
|
||||
},
|
||||
"body": "Let us keep this in draft until telemetry lands."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T03:45:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "leaferiksen",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/5151?v=4"
|
||||
},
|
||||
"body": "Telemetry hooks are now wired through the worker boundary."
|
||||
},
|
||||
{
|
||||
"__typename": "ConvertToDraftEvent",
|
||||
"createdAt": "2026-05-01T04:00:00Z",
|
||||
@@ -94,6 +112,35 @@
|
||||
},
|
||||
"state": "COMMENTED",
|
||||
"body": "Split looks good; leaving draft until telemetry is in."
|
||||
},
|
||||
{
|
||||
"__typename": "ReadyForReviewEvent",
|
||||
"createdAt": "2026-05-02T01:10:00Z",
|
||||
"actor": {
|
||||
"__typename": "User",
|
||||
"login": "leaferiksen",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/5151?v=4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-02T01:25:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "leaferiksen",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/5151?v=4"
|
||||
},
|
||||
"body": "Telemetry is now visible in the worker trace panel."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-02T01:40:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "kennethnym",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"body": "The context envelope boundary is clear now; ready for final review."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -21,6 +21,14 @@
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestCommit",
|
||||
"commit": {
|
||||
"committedDate": "2026-05-02T12:20:00Z",
|
||||
"abbreviatedOid": "8ad14c3",
|
||||
"messageHeadline": "Tighten dashboard spacing token scale"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestReview",
|
||||
"createdAt": "2026-05-02T15:40:00Z",
|
||||
@@ -32,6 +40,16 @@
|
||||
"state": "CHANGES_REQUESTED",
|
||||
"body": "The 12px sidebar gutter still feels cramped."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-02T15:55:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "mariahops",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/6161?v=4"
|
||||
},
|
||||
"body": "I will widen the sidebar gutter and rebalance the compact row padding."
|
||||
},
|
||||
{
|
||||
"__typename": "BaseRefChangedEvent",
|
||||
"createdAt": "2026-05-02T18:05:00Z",
|
||||
@@ -51,6 +69,37 @@
|
||||
},
|
||||
"body": "Updated the spacing tokens to align with the latest mock."
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestReview",
|
||||
"createdAt": "2026-05-02T18:45:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "kennethnym",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"state": "APPROVED",
|
||||
"body": "Spacing reads better now; compact dashboard surfaces still scan well."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-02T19:05:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "mariahops",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/6161?v=4"
|
||||
},
|
||||
"body": "I updated the visual comparison screenshot with the final spacing tokens."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-02T19:30:00Z",
|
||||
"author": {
|
||||
"__typename": "Bot",
|
||||
"login": "design-bot",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/in/97531?v=4"
|
||||
},
|
||||
"body": "Design token snapshot updated for dashboard spacing review."
|
||||
},
|
||||
{
|
||||
"__typename": "AutoMergeEnabledEvent",
|
||||
"createdAt": "2026-05-03T09:00:00Z",
|
||||
|
||||
@@ -26,6 +26,14 @@
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestCommit",
|
||||
"commit": {
|
||||
"committedDate": "2026-04-30T07:05:00Z",
|
||||
"abbreviatedOid": "6fd2a90",
|
||||
"messageHeadline": "Document standby promotion rollback step"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "ReopenedEvent",
|
||||
"createdAt": "2026-05-01T09:45:00Z",
|
||||
@@ -35,6 +43,37 @@
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/8181?v=4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T10:05:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "piperlane",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/8181?v=4"
|
||||
},
|
||||
"body": "Reopened after confirming the rollback path with infra."
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestReview",
|
||||
"createdAt": "2026-05-02T11:30:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "piperlane",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/8181?v=4"
|
||||
},
|
||||
"state": "APPROVED",
|
||||
"body": "Runbook steps are clear enough for the on-call handoff."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-02T11:45:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "kennethnym",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"body": "I added the approval note to the release handoff checklist."
|
||||
},
|
||||
{
|
||||
"__typename": "ClosedEvent",
|
||||
"createdAt": "2026-05-02T12:05:00Z",
|
||||
@@ -43,6 +82,16 @@
|
||||
"login": "piperlane",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/8181?v=4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-02T12:20:00Z",
|
||||
"author": {
|
||||
"__typename": "Bot",
|
||||
"login": "infra-bot",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/in/13579?v=4"
|
||||
},
|
||||
"body": "Runbook closure recorded for the standby promotion playbook."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -29,6 +29,25 @@
|
||||
"messageHeadline": "Hydrate issue pane from cached dashboard state"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestCommit",
|
||||
"commit": {
|
||||
"committedDate": "2026-05-01T10:45:00Z",
|
||||
"abbreviatedOid": "73ac918",
|
||||
"messageHeadline": "Preserve selected pull request while refreshing issues"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestReview",
|
||||
"createdAt": "2026-05-01T11:30:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "leaferiksen",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/5151?v=4"
|
||||
},
|
||||
"state": "APPROVED",
|
||||
"body": "Query cache behavior looks stable during rapid list refreshes."
|
||||
},
|
||||
{
|
||||
"__typename": "CrossReferencedEvent",
|
||||
"createdAt": "2026-05-01T12:00:00Z",
|
||||
@@ -61,6 +80,36 @@
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"body": "Cache hydration is now stable across refetches."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T12:45:00Z",
|
||||
"author": {
|
||||
"__typename": "Bot",
|
||||
"login": "novem-ci",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/in/54321?v=4"
|
||||
},
|
||||
"body": "Smoke suite passed with cached issue pane hydration enabled."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T13:10:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "leaferiksen",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/5151?v=4"
|
||||
},
|
||||
"body": "I also checked keyboard navigation during refresh and the selection anchor holds."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T13:25:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "kennethnym",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"body": "Great, I will mirror this behavior in the pull request detail pane next."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -38,6 +38,14 @@
|
||||
"messageHeadline": "Cache repository list for titlebar context switcher"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestCommit",
|
||||
"commit": {
|
||||
"committedDate": "2026-05-03T08:33:00Z",
|
||||
"abbreviatedOid": "4e19b62",
|
||||
"messageHeadline": "Reuse warm repository cache while picker refreshes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "ReviewRequestedEvent",
|
||||
"createdAt": "2026-05-03T08:40:00Z",
|
||||
@@ -62,6 +70,47 @@
|
||||
},
|
||||
"body": "The cached picker feels much faster under repo churn."
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestReview",
|
||||
"createdAt": "2026-05-03T10:20:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "leaferiksen",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/5151?v=4"
|
||||
},
|
||||
"state": "COMMENTED",
|
||||
"body": "One edge case remains when the repository disappears mid-refresh."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-03T11:15:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "kennethnym",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"body": "Added a fallback to keep the picker focused when the selected repo is removed."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-03T11:40:00Z",
|
||||
"author": {
|
||||
"__typename": "Bot",
|
||||
"login": "novem-ci",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/in/54321?v=4"
|
||||
},
|
||||
"body": "Repository picker regression suite passed against the warm cache path."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-03T12:05:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "leaferiksen",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/5151?v=4"
|
||||
},
|
||||
"body": "Confirmed the disappearing repository case no longer drops focus."
|
||||
},
|
||||
{
|
||||
"__typename": "AutoMergeEnabledEvent",
|
||||
"createdAt": "2026-05-04T06:30:00Z",
|
||||
|
||||
@@ -29,6 +29,14 @@
|
||||
"messageHeadline": "Add release handoff checklist panel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestCommit",
|
||||
"commit": {
|
||||
"committedDate": "2026-04-29T09:40:00Z",
|
||||
"abbreviatedOid": "51d09ee",
|
||||
"messageHeadline": "Add QA owner column to release checklist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-04-29T11:00:00Z",
|
||||
@@ -39,6 +47,16 @@
|
||||
},
|
||||
"body": "Release checklist is ready for QA."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-04-29T11:35:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "mariahops",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/6161?v=4"
|
||||
},
|
||||
"body": "QA handoff looks complete; I added sign-off notes to the release plan."
|
||||
},
|
||||
{
|
||||
"__typename": "ReviewRequestedEvent",
|
||||
"createdAt": "2026-04-30T09:20:00Z",
|
||||
@@ -75,6 +93,39 @@
|
||||
"label": {
|
||||
"name": "release-blocker"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "ReviewRequestedEvent",
|
||||
"createdAt": "2026-05-01T04:12:00Z",
|
||||
"actor": {
|
||||
"__typename": "User",
|
||||
"login": "kennethnym",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"requestedReviewer": {
|
||||
"__typename": "Team",
|
||||
"name": "release-engineering"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T04:14:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "kennethnym",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"body": "Release engineering is looped in for the final handoff validation."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T04:16:00Z",
|
||||
"author": {
|
||||
"__typename": "Bot",
|
||||
"login": "release-bot",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/in/24680?v=4"
|
||||
},
|
||||
"body": "Release checklist validation started for sprint planner handoff."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -35,6 +35,16 @@
|
||||
"abbreviatedOid": "be7a811"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T06:18:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "rorycraft",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/7171?v=4"
|
||||
},
|
||||
"body": "Force-pushed to fold in the final checklist copy edits."
|
||||
},
|
||||
{
|
||||
"__typename": "MilestonedEvent",
|
||||
"createdAt": "2026-05-01T06:35:00Z",
|
||||
@@ -57,6 +67,47 @@
|
||||
"name": "tests"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestReview",
|
||||
"createdAt": "2026-05-01T08:10:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "mariahops",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/6161?v=4"
|
||||
},
|
||||
"state": "CHANGES_REQUESTED",
|
||||
"body": "Please add the support rotation owner before this queues."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T09:25:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "rorycraft",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/7171?v=4"
|
||||
},
|
||||
"body": "Support rotation owner is now included in the handoff table."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T10:00:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "mariahops",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/6161?v=4"
|
||||
},
|
||||
"body": "That resolves my release blocker concern."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-01T11:30:00Z",
|
||||
"author": {
|
||||
"__typename": "Bot",
|
||||
"login": "release-bot",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/in/24680?v=4"
|
||||
},
|
||||
"body": "Release checklist now includes QA, support, and rollout owner sign-offs."
|
||||
},
|
||||
{
|
||||
"__typename": "AutoMergeEnabledEvent",
|
||||
"createdAt": "2026-05-02T02:15:00Z",
|
||||
|
||||
@@ -43,6 +43,26 @@
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/7171?v=4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-03T12:25:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "rorycraft",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/7171?v=4"
|
||||
},
|
||||
"body": "Linked the rollout issue so release notes can track the dependency."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-03T12:35:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "kennethnym",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
},
|
||||
"body": "I dismissed the stale review so the final approval reflects the latest force-push."
|
||||
},
|
||||
{
|
||||
"__typename": "ReviewDismissedEvent",
|
||||
"createdAt": "2026-05-03T12:50:00Z",
|
||||
@@ -62,6 +82,17 @@
|
||||
},
|
||||
"milestoneTitle": "May Release"
|
||||
},
|
||||
{
|
||||
"__typename": "PullRequestReview",
|
||||
"createdAt": "2026-05-04T08:05:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "mariahops",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/6161?v=4"
|
||||
},
|
||||
"state": "APPROVED",
|
||||
"body": "Support rotation is documented and the release checklist is ready."
|
||||
},
|
||||
{
|
||||
"__typename": "MergedEvent",
|
||||
"createdAt": "2026-05-04T18:10:00Z",
|
||||
@@ -79,6 +110,26 @@
|
||||
"login": "kennethnym",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/4242?v=4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-04T18:25:00Z",
|
||||
"author": {
|
||||
"__typename": "Bot",
|
||||
"login": "release-bot",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/in/24680?v=4"
|
||||
},
|
||||
"body": "Release handoff checklist was merged and the rollout issue has been updated."
|
||||
},
|
||||
{
|
||||
"__typename": "IssueComment",
|
||||
"createdAt": "2026-05-04T18:40:00Z",
|
||||
"author": {
|
||||
"__typename": "User",
|
||||
"login": "rorycraft",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/7171?v=4"
|
||||
},
|
||||
"body": "Thanks, I copied the final checklist into the release notes."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -66,122 +66,122 @@ pub(crate) struct PullRequestTimeline {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum PullRequestTimelineItem {
|
||||
Assigned {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
assignee: Option<TimelineActor>,
|
||||
},
|
||||
Unassigned {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
assignee: Option<TimelineActor>,
|
||||
},
|
||||
Comment {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
author: Option<TimelineActor>,
|
||||
body: String,
|
||||
body: Arc<str>,
|
||||
},
|
||||
Commit {
|
||||
committed_at: String,
|
||||
abbreviated_oid: String,
|
||||
message_headline: String,
|
||||
committed_at: Arc<str>,
|
||||
abbreviated_oid: Arc<str>,
|
||||
message_headline: Arc<str>,
|
||||
},
|
||||
Review {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
author: Option<TimelineActor>,
|
||||
state: String,
|
||||
body: String,
|
||||
state: Arc<str>,
|
||||
body: Arc<str>,
|
||||
},
|
||||
ReviewRequested {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
reviewer: Option<TimelineActor>,
|
||||
},
|
||||
ReviewRequestRemoved {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
reviewer: Option<TimelineActor>,
|
||||
},
|
||||
ReviewDismissed {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
Merged {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
Closed {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
Reopened {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
ConvertToDraft {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
ReadyForReview {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
HeadRefForcePushed {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
before_commit_oid: Option<String>,
|
||||
after_commit_oid: Option<String>,
|
||||
before_commit_oid: Option<Arc<str>>,
|
||||
after_commit_oid: Option<Arc<str>>,
|
||||
},
|
||||
BaseRefChanged {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
Labeled {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
label: String,
|
||||
label: Arc<str>,
|
||||
},
|
||||
Unlabeled {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
label: String,
|
||||
label: Arc<str>,
|
||||
},
|
||||
Milestoned {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
milestone_title: String,
|
||||
milestone_title: Arc<str>,
|
||||
},
|
||||
Demilestoned {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
milestone_title: String,
|
||||
milestone_title: Arc<str>,
|
||||
},
|
||||
Referenced {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
CrossReferenced {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
AutoMergeEnabled {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
AutoMergeDisabled {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
reason: String,
|
||||
reason: Arc<str>,
|
||||
},
|
||||
AddedToMergeQueue {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
RemovedFromMergeQueue {
|
||||
created_at: String,
|
||||
created_at: Arc<str>,
|
||||
actor: Option<TimelineActor>,
|
||||
},
|
||||
Other {
|
||||
typename: String,
|
||||
typename: Arc<str>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -215,9 +215,9 @@ pub(crate) enum ChangeType {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct TimelineActor {
|
||||
pub(crate) kind: String,
|
||||
pub(crate) name: String,
|
||||
pub(crate) avatar_url: Option<String>,
|
||||
pub(crate) kind: Arc<str>,
|
||||
pub(crate) name: Arc<str>,
|
||||
pub(crate) avatar_url: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
|
||||
@@ -583,8 +583,8 @@ impl query::QueryFn for FetchPullRequestTimeline {
|
||||
| actorFieldsOn::User => "User",
|
||||
}
|
||||
.into(),
|
||||
name: login,
|
||||
avatar_url: Some(avatar_url),
|
||||
name: login.into(),
|
||||
avatar_url: Some(avatar_url.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,23 +592,23 @@ impl query::QueryFn for FetchPullRequestTimeline {
|
||||
match actor {
|
||||
| assigneeFields::Bot(actor) => TimelineActor {
|
||||
kind: "Bot".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
name: actor.login.into(),
|
||||
avatar_url: Some(actor.avatar_url.into()),
|
||||
},
|
||||
| assigneeFields::Mannequin(actor) => TimelineActor {
|
||||
kind: "Mannequin".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
name: actor.login.into(),
|
||||
avatar_url: Some(actor.avatar_url.into()),
|
||||
},
|
||||
| assigneeFields::Organization(actor) => TimelineActor {
|
||||
kind: "Organization".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
name: actor.login.into(),
|
||||
avatar_url: Some(actor.avatar_url.into()),
|
||||
},
|
||||
| assigneeFields::User(actor) => TimelineActor {
|
||||
kind: "User".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
name: actor.login.into(),
|
||||
avatar_url: Some(actor.avatar_url.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -617,28 +617,28 @@ impl query::QueryFn for FetchPullRequestTimeline {
|
||||
match actor {
|
||||
| requestedReviewerFields::Bot(actor) => TimelineActor {
|
||||
kind: "Bot".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
name: actor.login.into(),
|
||||
avatar_url: Some(actor.avatar_url.into()),
|
||||
},
|
||||
| requestedReviewerFields::Mannequin(actor) => TimelineActor {
|
||||
kind: "Mannequin".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
name: actor.login.into(),
|
||||
avatar_url: Some(actor.avatar_url.into()),
|
||||
},
|
||||
| requestedReviewerFields::Team(actor) => TimelineActor {
|
||||
kind: "Team".into(),
|
||||
name: actor.name,
|
||||
name: actor.name.into(),
|
||||
avatar_url: None,
|
||||
},
|
||||
| requestedReviewerFields::User(actor) => TimelineActor {
|
||||
kind: "User".into(),
|
||||
name: actor.login,
|
||||
avatar_url: Some(actor.avatar_url),
|
||||
name: actor.login.into(),
|
||||
avatar_url: Some(actor.avatar_url.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_review_state(state: PullRequestReviewState) -> String {
|
||||
fn normalize_review_state(state: PullRequestReviewState) -> Arc<str> {
|
||||
match state {
|
||||
| PullRequestReviewState::PENDING => "PENDING",
|
||||
| PullRequestReviewState::COMMENTED => "COMMENTED",
|
||||
@@ -657,166 +657,168 @@ impl query::QueryFn for FetchPullRequestTimeline {
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::AssignedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Assigned {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
assignee: event.assignee.map(normalize_assignee),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::UnassignedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Unassigned {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
assignee: event.assignee.map(normalize_assignee),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::IssueComment(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Comment {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
author: event.author.map(normalize_actor),
|
||||
body: event.body,
|
||||
body: event.body.into(),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::PullRequestCommit(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Commit {
|
||||
committed_at: event.commit.committed_date,
|
||||
abbreviated_oid: event.commit.abbreviated_oid,
|
||||
message_headline: event.commit.message_headline,
|
||||
committed_at: event.commit.committed_date.into(),
|
||||
abbreviated_oid: event.commit.abbreviated_oid.into(),
|
||||
message_headline: event.commit.message_headline.into(),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::PullRequestReview(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Review {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
author: event.author.map(normalize_actor),
|
||||
state: normalize_review_state(event.state),
|
||||
body: event.body,
|
||||
body: event.body.into(),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::ReviewRequestedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::ReviewRequested {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
reviewer: event.requested_reviewer.map(normalize_requested_reviewer),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::ReviewRequestRemovedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::ReviewRequestRemoved {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
reviewer: event.requested_reviewer.map(normalize_requested_reviewer),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::ReviewDismissedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::ReviewDismissed {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::MergedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Merged {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::ClosedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Closed {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::ReopenedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Reopened {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::ConvertToDraftEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::ConvertToDraft {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::ReadyForReviewEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::ReadyForReview {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::HeadRefForcePushedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::HeadRefForcePushed {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
before_commit_oid: event.before_commit.map(|commit| commit.abbreviated_oid),
|
||||
after_commit_oid: event.after_commit.map(|commit| commit.abbreviated_oid),
|
||||
before_commit_oid: event
|
||||
.before_commit
|
||||
.map(|commit| commit.abbreviated_oid.into()),
|
||||
after_commit_oid: event.after_commit.map(|commit| commit.abbreviated_oid.into()),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::BaseRefChangedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::BaseRefChanged {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::LabeledEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Labeled {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
label: event.label.name,
|
||||
label: event.label.name.into(),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::UnlabeledEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Unlabeled {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
label: event.label.name,
|
||||
label: event.label.name.into(),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::MilestonedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Milestoned {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
milestone_title: event.milestone_title,
|
||||
milestone_title: event.milestone_title.into(),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::DemilestonedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Demilestoned {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
milestone_title: event.milestone_title,
|
||||
milestone_title: event.milestone_title.into(),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::ReferencedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::Referenced {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::CrossReferencedEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::CrossReferenced {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::AutoMergeEnabledEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::AutoMergeEnabled {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::AutoMergeDisabledEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::AutoMergeDisabled {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
reason: event.reason.unwrap_or_default(),
|
||||
reason: event.reason.unwrap_or_default().into(),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::AddedToMergeQueueEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::AddedToMergeQueue {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
PullRequestTimelineQueryNodeOnPullRequestTimelineItemsNodes::RemovedFromMergeQueueEvent(
|
||||
event,
|
||||
) => PullRequestTimelineItem::RemovedFromMergeQueue {
|
||||
created_at: event.created_at,
|
||||
created_at: event.created_at.into(),
|
||||
actor: event.actor.map(normalize_actor),
|
||||
},
|
||||
_ => PullRequestTimelineItem::Other {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
mod issue_list;
|
||||
mod pull_request_body_view;
|
||||
mod pull_request_change_view;
|
||||
pub(crate) mod pull_request_diff_view;
|
||||
mod pull_request_file_tree;
|
||||
pub(crate) mod pull_request_timeline_view;
|
||||
mod pull_request_view;
|
||||
mod screen;
|
||||
mod sidebar;
|
||||
|
||||
244
src/screen/dashboard/pull_request_body_view.rs
Normal file
244
src/screen/dashboard/pull_request_body_view.rs
Normal file
@@ -0,0 +1,244 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{
|
||||
AppContext, InteractiveElement, ParentElement, StatefulInteractiveElement, Styled, div, img,
|
||||
prelude::FluentBuilder,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
api, app,
|
||||
component::{
|
||||
font_icon::{FontIcon, font_icon},
|
||||
markdown::{self, MarkdownText},
|
||||
text::text,
|
||||
},
|
||||
query::{self, QueryStatus, read_query, use_query, watch_query},
|
||||
};
|
||||
|
||||
pub(crate) struct PullRequestBodyView {
|
||||
pr_query: query::Entity<api::issues::FetchPullRequest>,
|
||||
markdown_viewer: Option<gpui::Entity<MarkdownText>>,
|
||||
}
|
||||
|
||||
impl PullRequestBodyView {
|
||||
pub(crate) fn new(id: api::issues::Id, cx: &mut gpui::Context<Self>) -> Self {
|
||||
let mut v = Self {
|
||||
pr_query: use_query(api::issues::FetchPullRequest { id: id.clone() }, cx),
|
||||
markdown_viewer: None,
|
||||
};
|
||||
v.on_create(cx);
|
||||
v
|
||||
}
|
||||
|
||||
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
_ = watch_query(&self.pr_query, Self::load_markdown_content, cx).detach();
|
||||
}
|
||||
|
||||
fn load_markdown_content(
|
||||
&mut self,
|
||||
_query: &query::Entity<api::issues::FetchPullRequest>,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) {
|
||||
let maybe_content = {
|
||||
let data = read_query(&self.pr_query, cx);
|
||||
if let QueryStatus::Loaded(pr) = data {
|
||||
Some(Arc::clone(&pr.body))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
self.markdown_viewer = maybe_content.map(|content| cx.new(|cx| markdown::new(content, cx)));
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn pr_description(
|
||||
&self,
|
||||
pr: &api::issues::DetailedPullRequest,
|
||||
cx: &gpui::Context<Self>,
|
||||
) -> gpui::Div {
|
||||
let theme = app::current_theme(cx);
|
||||
|
||||
let mut status_pill = div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.px_2()
|
||||
.rounded_full();
|
||||
|
||||
match pr.state {
|
||||
| api::issues::PullRequestState::Open => {
|
||||
status_pill = status_pill
|
||||
.bg(theme.colors.success_solid)
|
||||
.child(
|
||||
font_icon(FontIcon::PullRequestArrow)
|
||||
.size_3()
|
||||
.text_color(theme.colors.success_on_solid),
|
||||
)
|
||||
.child(
|
||||
text("Open")
|
||||
.text_color(theme.colors.success_on_solid)
|
||||
.text_xs(),
|
||||
);
|
||||
}
|
||||
| api::issues::PullRequestState::Closed => {
|
||||
status_pill = status_pill
|
||||
.bg(theme.colors.danger_solid)
|
||||
.child(
|
||||
font_icon(FontIcon::PullRequestClosed)
|
||||
.size_3()
|
||||
.text_color(theme.colors.danger_on_solid),
|
||||
)
|
||||
.child(
|
||||
text("Closed")
|
||||
.text_color(theme.colors.danger_on_solid)
|
||||
.text_xs(),
|
||||
);
|
||||
}
|
||||
| api::issues::PullRequestState::Merged => {
|
||||
status_pill = status_pill.bg(theme.colors.accent_solid).child(
|
||||
text("Merged")
|
||||
.text_color(theme.colors.accent_on_solid)
|
||||
.text_xs(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let merge_text = pr.author.as_ref().map(|author| {
|
||||
let base_branch = &pr.base_branch_name;
|
||||
let head_branch = &pr.head_branch_name;
|
||||
let str = format!(
|
||||
"{} requested to merge {} into {}",
|
||||
author.login, head_branch, base_branch
|
||||
);
|
||||
|
||||
let head_branch_text_offset = author.login.len() + 20;
|
||||
let base_branch_text_offset = head_branch_text_offset + head_branch.len() + 6;
|
||||
|
||||
let highlights = [
|
||||
(
|
||||
0..author.login.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
head_branch_text_offset..head_branch_text_offset + head_branch.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
color: Some(theme.colors.accent_fg.into()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
base_branch_text_offset..base_branch_text_offset + base_branch.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
color: Some(theme.colors.accent_fg.into()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
(
|
||||
author,
|
||||
gpui::StyledText::new(str).with_highlights(highlights),
|
||||
)
|
||||
});
|
||||
|
||||
let pr_title = gpui::SharedString::new(Arc::clone(&pr.title));
|
||||
|
||||
let metadata_line =
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.gap_2()
|
||||
.when_some(merge_text, |it, (author, t)| {
|
||||
it.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap_1p5()
|
||||
.child(img(author.avatar_url.as_ref()).size_4().rounded_full())
|
||||
.child(
|
||||
div()
|
||||
.min_w_0()
|
||||
.w_full()
|
||||
.text_color(theme.colors.text)
|
||||
.text_xs()
|
||||
.font_weight(gpui::FontWeight::LIGHT)
|
||||
.opacity(0.8)
|
||||
.child(t),
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.bg(theme.colors.surface)
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.w_full()
|
||||
.px_3p5()
|
||||
.py_3()
|
||||
.border_b_1()
|
||||
.border_color(theme.colors.border_muted)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.items_start()
|
||||
.child(text(pr_title).w_full().text_xl().mb_1())
|
||||
.child(metadata_line),
|
||||
)
|
||||
.child(div().flex().flex_col().items_end().gap_1().when_some(
|
||||
pr.created_at,
|
||||
|it, created_at| {
|
||||
it.child(
|
||||
text(created_at.format("%Y-%m-%d %H:%M").to_string())
|
||||
.opacity(0.5)
|
||||
.text_xs(),
|
||||
)
|
||||
.child(status_pill)
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div().flex_1().min_h_0().w_full().child(
|
||||
div()
|
||||
.id("pr-body-content")
|
||||
.size_full()
|
||||
.overflow_y_scroll()
|
||||
.when_some(self.markdown_viewer.as_ref(), |it, viewer| {
|
||||
it.child(div().w_full().p_3p5().child(viewer.clone()))
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::Render for PullRequestBodyView {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::prelude::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
match read_query(&self.pr_query, cx) {
|
||||
| QueryStatus::Err(e) => div().size_full().child(format!("{:?}", e)),
|
||||
| QueryStatus::Loading => div().size_full().child("loading pr content"),
|
||||
| QueryStatus::Loaded(pr) => self.pr_description(pr, cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/screen/dashboard/pull_request_timeline_view.rs
Normal file
87
src/screen/dashboard/pull_request_timeline_view.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{ParentElement, Styled, div, px};
|
||||
|
||||
use crate::{
|
||||
api, app,
|
||||
component::text::text,
|
||||
query::{self, QueryStatus, read_query, use_query},
|
||||
};
|
||||
|
||||
pub(crate) struct PullRequestTimelineView {
|
||||
timeline_list_state: gpui::ListState,
|
||||
pr_timeline_query: query::Entity<api::issues::FetchPullRequestTimeline>,
|
||||
}
|
||||
|
||||
impl PullRequestTimelineView {
|
||||
pub(crate) fn new(id: api::issues::Id, cx: &mut gpui::Context<Self>) -> Self {
|
||||
Self {
|
||||
timeline_list_state: gpui::ListState::new(0, gpui::ListAlignment::Top, px(100.)),
|
||||
pr_timeline_query: use_query(
|
||||
api::issues::FetchPullRequestTimeline {
|
||||
id,
|
||||
first: 10,
|
||||
after: None,
|
||||
},
|
||||
cx,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::Render for PullRequestTimelineView {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::prelude::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
match read_query(&self.pr_timeline_query, cx) {
|
||||
| QueryStatus::Loading => div().child("loading"),
|
||||
| QueryStatus::Err(_) => div().child("error"),
|
||||
|
||||
| QueryStatus::Loaded(data) => {
|
||||
let items = data.items.iter().map(|item| match item {
|
||||
| api::issues::PullRequestTimelineItem::Comment { body, author, .. } => {
|
||||
comment_card(author.as_ref(), body.clone(), cx)
|
||||
}
|
||||
| _ => div(),
|
||||
});
|
||||
|
||||
div().flex().flex_col().p_2().gap_2().children(items)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn comment_card(
|
||||
author: Option<&api::issues::TimelineActor>,
|
||||
body: Arc<str>,
|
||||
cx: &gpui::App,
|
||||
) -> gpui::Div {
|
||||
let theme = app::current_theme(cx);
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.border_1()
|
||||
.border_color(theme.colors.border_muted)
|
||||
.rounded_md()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.px_2()
|
||||
.pt_1p5()
|
||||
.child(
|
||||
div().text_xs().child(
|
||||
match author.map(|it| it.name.clone()) {
|
||||
| Some(author_name) => text(gpui::SharedString::from(author_name)),
|
||||
| None => text("Unknown author"),
|
||||
}
|
||||
.text_color(theme.colors.text_subtle)
|
||||
.font_weight(gpui::FontWeight::SEMIBOLD),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(text(gpui::SharedString::from(body)).pl_7().text_sm().p_2())
|
||||
}
|
||||
@@ -1,31 +1,30 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{
|
||||
AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled,
|
||||
div, img, prelude::FluentBuilder,
|
||||
};
|
||||
use gpui::{AppContext, IntoElement, ParentElement, Styled, div};
|
||||
|
||||
use crate::{
|
||||
api::{self},
|
||||
app,
|
||||
component::{
|
||||
button::{self, Button, button},
|
||||
font_icon::{FontIcon, font_icon},
|
||||
markdown::{self, MarkdownText},
|
||||
font_icon::FontIcon,
|
||||
segmented_control::segmented_control,
|
||||
text::text,
|
||||
},
|
||||
query::{self, QueryStatus, read_query, use_query, watch_query},
|
||||
screen::dashboard::pull_request_change_view::{self, PullRequestChangeView},
|
||||
screen::dashboard::{
|
||||
pull_request_body_view::PullRequestBodyView,
|
||||
pull_request_change_view::{self, PullRequestChangeView},
|
||||
pull_request_timeline_view::PullRequestTimelineView,
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) struct PullRequestView {
|
||||
current_tab: Tab,
|
||||
|
||||
markdown_viewer: Option<gpui::Entity<MarkdownText>>,
|
||||
pr_content: Option<PullRequestContent>,
|
||||
diff_view: Option<gpui::Entity<PullRequestChangeView>>,
|
||||
}
|
||||
|
||||
pull_request_query: Option<query::Entity<api::issues::FetchPullRequest>>,
|
||||
struct PullRequestContent {
|
||||
body: gpui::Entity<PullRequestBodyView>,
|
||||
timeline: gpui::Entity<PullRequestTimelineView>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
@@ -37,9 +36,8 @@ enum Tab {
|
||||
pub fn new(_cx: &mut gpui::Context<PullRequestView>) -> PullRequestView {
|
||||
PullRequestView {
|
||||
current_tab: Tab::PullRequestBody,
|
||||
markdown_viewer: None,
|
||||
pr_content: None,
|
||||
diff_view: None,
|
||||
pull_request_query: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,60 +47,12 @@ impl PullRequestView {
|
||||
id: api::issues::Id,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) {
|
||||
let query = use_query(api::issues::FetchPullRequest { id }, cx);
|
||||
|
||||
self.pull_request_query = Some(query.clone());
|
||||
self.current_tab = Tab::PullRequestBody;
|
||||
|
||||
_ = watch_query(&query, Self::sync_pull_request_query, cx).detach();
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn sync_pull_request_query(
|
||||
&mut self,
|
||||
_query: &query::Entity<api::issues::FetchPullRequest>,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) {
|
||||
self.load_markdown_content(cx);
|
||||
self.load_pr_diff(cx);
|
||||
}
|
||||
|
||||
fn load_markdown_content(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
let Some(query) = &self.pull_request_query else {
|
||||
return;
|
||||
};
|
||||
|
||||
let maybe_content = {
|
||||
let data = read_query(&query, cx);
|
||||
if let QueryStatus::Loaded(pr) = data {
|
||||
Some(Arc::clone(&pr.body))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
self.markdown_viewer = maybe_content.map(|content| cx.new(|cx| markdown::new(content, cx)));
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn load_pr_diff(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
let Some(query) = &self.pull_request_query else {
|
||||
return;
|
||||
};
|
||||
|
||||
let pr_id = {
|
||||
let data = read_query(&query, cx);
|
||||
if let QueryStatus::Loaded(pr) = data {
|
||||
Some(pr.id.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
self.diff_view = pr_id.map(|id| cx.new(|cx| pull_request_change_view::new(id, cx)));
|
||||
|
||||
self.pr_content = Some(PullRequestContent {
|
||||
body: cx.new(|cx| PullRequestBodyView::new(id.clone(), cx)),
|
||||
timeline: cx.new(|cx| PullRequestTimelineView::new(id.clone(), cx)),
|
||||
});
|
||||
self.diff_view = Some(cx.new(|cx| pull_request_change_view::new(id, cx)));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -143,182 +93,6 @@ impl PullRequestView {
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn pr_content(
|
||||
&self,
|
||||
pr: &api::issues::DetailedPullRequest,
|
||||
cx: &gpui::Context<Self>,
|
||||
) -> gpui::AnyElement {
|
||||
let theme = app::current_theme(cx);
|
||||
|
||||
let mut status_pill = div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.px_2()
|
||||
.rounded_full();
|
||||
|
||||
match pr.state {
|
||||
| api::issues::PullRequestState::Open => {
|
||||
status_pill = status_pill
|
||||
.bg(theme.colors.success_solid)
|
||||
.child(
|
||||
font_icon(FontIcon::PullRequestArrow)
|
||||
.size_3()
|
||||
.text_color(theme.colors.success_on_solid),
|
||||
)
|
||||
.child(
|
||||
text("Open")
|
||||
.text_color(theme.colors.success_on_solid)
|
||||
.text_xs(),
|
||||
);
|
||||
}
|
||||
| api::issues::PullRequestState::Closed => {
|
||||
status_pill = status_pill
|
||||
.bg(theme.colors.danger_solid)
|
||||
.child(
|
||||
font_icon(FontIcon::PullRequestClosed)
|
||||
.size_3()
|
||||
.text_color(theme.colors.danger_on_solid),
|
||||
)
|
||||
.child(
|
||||
text("Closed")
|
||||
.text_color(theme.colors.danger_on_solid)
|
||||
.text_xs(),
|
||||
);
|
||||
}
|
||||
| api::issues::PullRequestState::Merged => {
|
||||
status_pill = status_pill.bg(theme.colors.accent_solid).child(
|
||||
text("Merged")
|
||||
.text_color(theme.colors.accent_on_solid)
|
||||
.text_xs(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let merge_text = pr.author.as_ref().map(|author| {
|
||||
let base_branch = &pr.base_branch_name;
|
||||
let head_branch = &pr.head_branch_name;
|
||||
let str = format!(
|
||||
"{} requested to merge {} into {}",
|
||||
author.login, head_branch, base_branch
|
||||
);
|
||||
|
||||
let head_branch_text_offset = author.login.len() + 20;
|
||||
let base_branch_text_offset = head_branch_text_offset + head_branch.len() + 6;
|
||||
|
||||
let highlights = [
|
||||
(
|
||||
0..author.login.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
head_branch_text_offset..head_branch_text_offset + head_branch.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
color: Some(theme.colors.accent_fg.into()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
base_branch_text_offset..base_branch_text_offset + base_branch.len(),
|
||||
gpui::HighlightStyle {
|
||||
font_weight: Some(gpui::FontWeight::BOLD),
|
||||
color: Some(theme.colors.accent_fg.into()),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
(
|
||||
author,
|
||||
gpui::StyledText::new(str).with_highlights(highlights),
|
||||
)
|
||||
});
|
||||
|
||||
let pr_title = gpui::SharedString::new(Arc::clone(&pr.title));
|
||||
|
||||
let metadata_line =
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.gap_2()
|
||||
.when_some(merge_text, |it, (author, t)| {
|
||||
it.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap_1p5()
|
||||
.child(img(author.avatar_url.as_ref()).size_4().rounded_full())
|
||||
.child(
|
||||
div()
|
||||
.min_w_0()
|
||||
.w_full()
|
||||
.text_color(theme.colors.text)
|
||||
.text_xs()
|
||||
.font_weight(gpui::FontWeight::LIGHT)
|
||||
.opacity(0.8)
|
||||
.child(t),
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.bg(theme.colors.surface)
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.w_full()
|
||||
.px_3p5()
|
||||
.py_3()
|
||||
.border_b_1()
|
||||
.border_color(theme.colors.border_muted)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.items_start()
|
||||
.child(text(pr_title).w_full().text_xl().mb_1())
|
||||
.child(metadata_line),
|
||||
)
|
||||
.child(div().flex().flex_col().items_end().gap_1().when_some(
|
||||
pr.created_at,
|
||||
|it, created_at| {
|
||||
it.child(
|
||||
text(created_at.format("%Y-%m-%d %H:%M").to_string())
|
||||
.opacity(0.5)
|
||||
.text_xs(),
|
||||
)
|
||||
.child(status_pill)
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div().flex_1().min_h_0().w_full().child(
|
||||
div()
|
||||
.id("pr-body-content")
|
||||
.size_full()
|
||||
.overflow_y_scroll()
|
||||
.when_some(self.markdown_viewer.as_ref(), |it, viewer| {
|
||||
it.child(div().w_full().p_3p5().child(viewer.clone()))
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::Render for PullRequestView {
|
||||
@@ -327,31 +101,43 @@ impl gpui::Render for PullRequestView {
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let theme = app::current_theme(cx);
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.toolbar(cx))
|
||||
.child(match &self.pull_request_query {
|
||||
| Some(q) => {
|
||||
match read_query(q, cx) {
|
||||
| QueryStatus::Loaded(pr) => match (&self.diff_view, self.current_tab) {
|
||||
| (Some(diff_view), Tab::DiffView) => diff_view.clone().into_any_element(),
|
||||
| _ => self.pr_content(pr, cx),
|
||||
.child(
|
||||
match (&self.diff_view, &self.pr_content, self.current_tab) {
|
||||
| (Some(diff_view), _, Tab::DiffView) => diff_view.clone().into_any_element(),
|
||||
|
||||
| (_, Some(pr_content), Tab::PullRequestBody) => div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.border_r_1()
|
||||
.border_color(theme.colors.border_muted)
|
||||
.child(pr_content.body.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.bg(theme.colors.surface)
|
||||
.child(pr_content.timeline.clone()),
|
||||
)
|
||||
.into_any_element(),
|
||||
|
||||
| (_, None, Tab::PullRequestBody) => {
|
||||
div().size_full().child("no pr selected").into_any_element()
|
||||
}
|
||||
|
||||
| _ => panic!("should not happen"),
|
||||
},
|
||||
|
||||
| QueryStatus::Err(e) => div()
|
||||
.size_full()
|
||||
.child(format!("{:?}", e))
|
||||
.into_any_element(),
|
||||
| QueryStatus::Loading => div()
|
||||
.size_full()
|
||||
.child("loading pr content")
|
||||
.into_any_element(),
|
||||
}
|
||||
}
|
||||
|
||||
| None => div().size_full().child("no pr selected").into_any_element(),
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ pub(crate) fn mocha() -> Theme {
|
||||
colors: ThemeColors {
|
||||
background: hex(0x1e1e2e),
|
||||
surface: hex(0x181825),
|
||||
surface_elevated: hex(0x45475a),
|
||||
surface_elevated: hex(0x363a4f),
|
||||
surface_button: linear_gradient(
|
||||
180.,
|
||||
linear_color_stop(hex(0x4f5068), 0.),
|
||||
|
||||
Reference in New Issue
Block a user