mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-30 16:50:47 +08:00
Start review from code-comment on revision diff
Co-authored-by: Rūdolfs Ošiņš <rudolfs@osins.org>
This commit is contained in:
co-authored by
Rūdolfs Ošiņš
parent
e0aafa7de2
commit
ec49ecbd36
@@ -12,5 +12,5 @@ export type Patch = {
|
||||
assignees: Array<Author>;
|
||||
labels: Array<string>;
|
||||
timestamp: number;
|
||||
revisionCount: number;
|
||||
revisionIds: Array<string>;
|
||||
};
|
||||
|
||||
@@ -35,7 +35,8 @@ pub struct Patch {
|
||||
labels: Vec<cob::Label>,
|
||||
#[ts(type = "number")]
|
||||
timestamp: cob::Timestamp,
|
||||
revision_count: usize,
|
||||
#[ts(as = "Vec<String>")]
|
||||
revision_ids: Vec<patch::RevisionId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -63,7 +64,7 @@ impl Patch {
|
||||
.collect::<Vec<_>>(),
|
||||
labels: patch.labels().cloned().collect::<Vec<_>>(),
|
||||
timestamp: patch.timestamp(),
|
||||
revision_count: patch.revisions().count(),
|
||||
revision_ids: patch.revisions().map(|(id, _)| id).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { PatchStatus } from "@app/views/repo/router";
|
||||
import type { Patch } from "@bindings/cob/patch/Patch";
|
||||
|
||||
import { draftReviewStorage } from "@app/lib/draftReviewStorage";
|
||||
import { cachedDiffStats } from "@app/lib/invoke";
|
||||
import { push } from "@app/lib/router";
|
||||
import {
|
||||
@@ -26,6 +27,10 @@
|
||||
}
|
||||
|
||||
const { focussed, patch, rid, status }: Props = $props();
|
||||
|
||||
const hasDraftReview = $derived(
|
||||
patch.revisionIds.some(id => draftReviewStorage.hasForRevision(id)),
|
||||
);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -103,6 +108,18 @@
|
||||
</div>
|
||||
|
||||
<div class="global-flex" style:margin-left="auto">
|
||||
{#if hasDraftReview}
|
||||
<div
|
||||
class="txt-body-m-regular"
|
||||
style:white-space="nowrap"
|
||||
style:border="1px solid var(--color-border-subtle)"
|
||||
style:border-radius="var(--border-radius-sm)"
|
||||
style:padding="0.125rem 0.5rem"
|
||||
style:color="var(--color-text-primary)">
|
||||
Review in progress
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#await cachedDiffStats(rid, patch.base, patch.head) then stats}
|
||||
<DiffStatBadge {stats} />
|
||||
{/await}
|
||||
@@ -120,7 +137,7 @@
|
||||
style:padding="0 0.5rem"
|
||||
style:color="var(--color-text-tertiary)">
|
||||
<Icon name="revision" />
|
||||
{patch.revisionCount}
|
||||
{patch.revisionIds.length}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -148,7 +148,6 @@
|
||||
if ("draft" in review) {
|
||||
draftReviewStorage.addComment(review.id, {
|
||||
body,
|
||||
embeds,
|
||||
location: location!,
|
||||
});
|
||||
} else {
|
||||
@@ -178,7 +177,6 @@
|
||||
if ("draft" in review) {
|
||||
draftReviewStorage.updateComment(review.id, commentId, {
|
||||
body,
|
||||
embeds,
|
||||
});
|
||||
} else {
|
||||
await invoke("edit_patch", {
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<script lang="ts">
|
||||
import type { CodeComments } from "@app/components/Diff.svelte";
|
||||
import type { Author } from "@bindings/cob/Author";
|
||||
import type { Revision } from "@bindings/cob/patch/Revision";
|
||||
import type { CodeLocation } from "@bindings/cob/thread/CodeLocation";
|
||||
import type { Embed } from "@bindings/cob/thread/Embed";
|
||||
import type { Thread } from "@bindings/cob/thread/Thread";
|
||||
import type { Config } from "@bindings/config/Config";
|
||||
|
||||
import { draftReviewStorage } from "@app/lib/draftReviewStorage";
|
||||
import { nodeRunning } from "@app/lib/events";
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
import * as roles from "@app/lib/roles";
|
||||
import { publicKeyFromDid } from "@app/lib/utils";
|
||||
import { didFromPublicKey, publicKeyFromDid } from "@app/lib/utils";
|
||||
|
||||
import { announce } from "@app/components/AnnounceSwitch.svelte";
|
||||
import Changes from "@app/components/Changes.svelte";
|
||||
@@ -35,6 +38,85 @@
|
||||
hideDescription = false,
|
||||
}: Props = $props();
|
||||
|
||||
const currentUserAuthor: Author = $derived({
|
||||
did: didFromPublicKey(config.publicKey),
|
||||
alias: config.alias ?? undefined,
|
||||
});
|
||||
|
||||
const draftReview = $derived(
|
||||
draftReviewStorage.getForRevision(revision.id, currentUserAuthor),
|
||||
);
|
||||
|
||||
const hasPublishedReview = $derived(
|
||||
revision.reviews?.some(r => r.author.did === currentUserAuthor.did) ??
|
||||
false,
|
||||
);
|
||||
|
||||
const codeCommentThreads: Thread<CodeLocation>[] = $derived(
|
||||
draftReview
|
||||
? (draftReview.comments
|
||||
.filter(c => c.location && !c.replyTo)
|
||||
.map(root => ({
|
||||
root,
|
||||
replies: draftReview.comments.filter(c => c.replyTo === root.id),
|
||||
})) as Thread<CodeLocation>[])
|
||||
: [],
|
||||
);
|
||||
|
||||
async function createCodeComment(
|
||||
body: string,
|
||||
_embeds: Embed[],
|
||||
_replyTo?: string,
|
||||
location?: CodeLocation,
|
||||
) {
|
||||
if (!location) return;
|
||||
try {
|
||||
let draftId = draftReview?.id;
|
||||
if (!draftId) {
|
||||
draftId = draftReviewStorage.create(rid, revision.id);
|
||||
}
|
||||
draftReviewStorage.addComment(draftId, { body, location });
|
||||
} catch (error) {
|
||||
console.error("Creating code comment failed", error);
|
||||
} finally {
|
||||
await loadPatch();
|
||||
}
|
||||
}
|
||||
|
||||
async function editCodeComment(
|
||||
commentId: string,
|
||||
body: string,
|
||||
_embeds: Embed[],
|
||||
) {
|
||||
if (!draftReview) return;
|
||||
draftReviewStorage.updateComment(draftReview.id, commentId, {
|
||||
body,
|
||||
});
|
||||
await loadPatch();
|
||||
}
|
||||
|
||||
async function deleteCodeComment(commentId: string) {
|
||||
if (!draftReview) return;
|
||||
draftReviewStorage.deleteComment(draftReview.id, commentId);
|
||||
await loadPatch();
|
||||
}
|
||||
|
||||
const codeComments: CodeComments | undefined = $derived(
|
||||
hasPublishedReview
|
||||
? undefined
|
||||
: {
|
||||
config,
|
||||
createComment: createCodeComment,
|
||||
editComment: editCodeComment,
|
||||
deleteComment: deleteCodeComment,
|
||||
repoDelegates,
|
||||
rid,
|
||||
threads: codeCommentThreads,
|
||||
canReply: true,
|
||||
disableAttachments: "Publish your review to attach files",
|
||||
},
|
||||
);
|
||||
|
||||
const commentThreads = $derived(
|
||||
((revision.discussion &&
|
||||
revision.discussion
|
||||
@@ -206,4 +288,4 @@
|
||||
{repoDelegates}
|
||||
{rid} />
|
||||
|
||||
<Changes {rid} {patchId} {revision} />
|
||||
<Changes {rid} {patchId} {revision} {codeComments} />
|
||||
|
||||
@@ -5,7 +5,6 @@ import type { Verdict } from "@bindings/cob/patch/Verdict";
|
||||
import type { CodeLocation } from "@bindings/cob/thread/CodeLocation";
|
||||
import type { CodeRange } from "@bindings/cob/thread/CodeRange";
|
||||
import type { Comment } from "@bindings/cob/thread/Comment";
|
||||
import type { Embed } from "@bindings/cob/thread/Embed";
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -106,6 +105,12 @@ export const draftReviewStorage = {
|
||||
return id;
|
||||
},
|
||||
|
||||
hasForRevision(revisionId: string): boolean {
|
||||
return Object.values(storage.value).some(
|
||||
review => review.revision === revisionId,
|
||||
);
|
||||
},
|
||||
|
||||
update(
|
||||
id: string,
|
||||
props: { summary: string; verdict: Verdict | undefined; labels: string[] },
|
||||
@@ -138,7 +143,6 @@ export const draftReviewStorage = {
|
||||
id: string,
|
||||
comment: {
|
||||
body: string;
|
||||
embeds: Embed[];
|
||||
location: CodeLocation;
|
||||
},
|
||||
): string {
|
||||
@@ -159,7 +163,6 @@ export const draftReviewStorage = {
|
||||
commentId: string,
|
||||
comment: {
|
||||
body: string;
|
||||
embeds: Embed[];
|
||||
},
|
||||
) {
|
||||
updateStoredDraftReview(id, review => {
|
||||
|
||||
+20
-10
@@ -171,9 +171,18 @@
|
||||
...(selectedRevision.reviews ?? []),
|
||||
].filter((review): review is Review | DraftReview => Boolean(review)),
|
||||
);
|
||||
const hasOwnReview = $derived(
|
||||
const ownDraftReview = $derived(
|
||||
reviewsOfSelectedRevision.find(
|
||||
value =>
|
||||
value.author.did === didFromPublicKey(config.publicKey) &&
|
||||
"draft" in value,
|
||||
),
|
||||
);
|
||||
const hasOwnPublishedReview = $derived(
|
||||
reviewsOfSelectedRevision.some(
|
||||
value => value.author.did === didFromPublicKey(config.publicKey),
|
||||
value =>
|
||||
value.author.did === didFromPublicKey(config.publicKey) &&
|
||||
!("draft" in value),
|
||||
),
|
||||
);
|
||||
const patchDescription = $derived(
|
||||
@@ -327,12 +336,11 @@
|
||||
patchId={patch.id} />
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={hasOwnReview}
|
||||
disabled={hasOwnPublishedReview}
|
||||
onclick={() => {
|
||||
const id = draftReviewStorage.create(
|
||||
repo.rid,
|
||||
selectedRevision.id,
|
||||
);
|
||||
const id =
|
||||
ownDraftReview?.id ??
|
||||
draftReviewStorage.create(repo.rid, selectedRevision.id);
|
||||
void router.push({
|
||||
resource: "repo.patch",
|
||||
rid: repo.rid,
|
||||
@@ -341,13 +349,15 @@
|
||||
status,
|
||||
});
|
||||
}}
|
||||
title={hasOwnReview
|
||||
title={hasOwnPublishedReview
|
||||
? "You already created a review for this revision"
|
||||
: "Review revision"}>
|
||||
: ownDraftReview
|
||||
? "Continue review"
|
||||
: "Review revision"}>
|
||||
<Icon name="comment" />
|
||||
<span
|
||||
class="txt-body-m-regular global-hide-on-medium-desktop-down">
|
||||
Review revision
|
||||
{ownDraftReview ? "Continue review" : "Review revision"}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user