Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add patch reviewers in sidebar
Sebastian Martinez committed 2 years ago
commit 0e6be193731812209217b34888dd2651cddaf354
parent 31be78144f3f6828853c5394378c9164b1a900d2
10 files changed +119 -44
modified src/components/Icon.svelte
@@ -13,6 +13,7 @@
    | "chevron-up"
    | "clipboard"
    | "clipboard-small"
+
    | "cross"
    | "diff"
    | "ellipsis"
    | "exclamation"
@@ -174,6 +175,18 @@
         14.3536C16.1583 14.5488 15.8417 14.5488 15.6464 14.3536L12.3536
         11.0607C12.1583 10.8654 11.8417 10.8654 11.6464 11.0607L8.35355
         14.3536C8.15829 14.5488 7.84171 14.5488 7.64645 14.3536Z" />
+
  {:else if name === "cross"}
+
    <path
+
      fill-rule="evenodd"
+
      clip-rule="evenodd"
+
      d="M5.64645 5.64645C5.84171 5.45118 6.15829 5.45118 6.35355 5.64645L12
+
      11.2929L17.6464 5.64645C17.8417 5.45118 18.1583 5.45118 18.3536
+
      5.64645C18.5488 5.84171 18.5488 6.15829 18.3536 6.35355L12.7071 12L18.3536
+
      17.6464C18.5488 17.8417 18.5488 18.1583 18.3536 18.3536C18.1583 18.5488
+
      17.8417 18.5488 17.6464 18.3536L12 12.7071L6.35355 18.3536C6.15829 18.5488
+
      5.84171 18.5488 5.64645 18.3536C5.45118 18.1583 5.45118 17.8417 5.64645
+
      17.6464L11.2929 12L5.64645 6.35355C5.45118 6.15829 5.45118 5.84171 5.64645
+
      5.64645Z" />
  {:else if name === "chevron-left"}
    <path
      fill-rule="evenodd"
modified src/views/projects/Cob/AssigneeInput.svelte
@@ -60,9 +60,6 @@
    gap: 0.5rem;
    margin-bottom: 1.25rem;
  }
-
  .empty {
-
    color: var(--color-foreground-5);
-
  }

  .chip-content {
    display: flex;
@@ -110,7 +107,7 @@
        </div>
      </Chip>
    {:else}
-
      <div class="empty">No assignees</div>
+
      <div class="txt-missing">No assignees</div>
    {/each}
  </div>
  {#if editInProgress || action === "create"}
modified src/views/projects/Cob/Revision.svelte
@@ -41,14 +41,14 @@

  const api = new HttpdClient(baseUrl);

-
  function formatVerdict(revision: string, verdict?: string | null) {
+
  function formatVerdict(verdict?: string | null) {
    switch (verdict) {
      case "accept":
-
        return `accepted revision ${utils.formatObjectId(revision)}`;
+
        return "accepted revision";
      case "reject":
-
        return `rejected revision ${utils.formatObjectId(revision)}`;
+
        return "rejected revision";
      default:
-
        return `left a comment on revision ${utils.formatObjectId(revision)}`;
+
        return "left a review";
    }
  }

@@ -341,14 +341,14 @@
            </div>
          </div>
        {:else if element.type === "review"}
-
          {@const [revisionId, author, review] = element.inner}
+
          {@const [author, review] = element.inner}
          {#if review.comment}
            <div
              class="review"
-
              class:positive-review={element.inner[2].verdict === "accept"}
-
              class:negative-review={element.inner[2].verdict === "reject"}>
+
              class:positive-review={review.verdict === "accept"}
+
              class:negative-review={review.verdict === "reject"}>
              <CommentComponent
-
                caption={formatVerdict(revisionId, review.verdict)}
+
                caption={formatVerdict(review.verdict)}
                authorId={author}
                authorAlias={review.author.alias}
                authorAliasColor={aliasColorForVerdict(review.verdict)}
@@ -359,28 +359,28 @@
          {:else}
            <div
              class="action layout-desktop txt-tiny"
-
              class:positive-review={element.inner[2].verdict === "accept"}
-
              class:negative-review={element.inner[2].verdict === "reject"}>
+
              class:positive-review={review.verdict === "accept"}
+
              class:negative-review={review.verdict === "reject"}>
              <div class="action-content">
                <Authorship
                  authorId={author}
                  authorAlias={review.author.alias}
                  authorAliasColor={aliasColorForVerdict(review.verdict)}
                  timestamp={element.timestamp}>
-
                  {formatVerdict(revisionId, review.verdict)}
+
                  {formatVerdict(review.verdict)}
                </Authorship>
              </div>
            </div>
            <div
              class="layout-mobile txt-tiny"
-
              class:positive-review={element.inner[2].verdict === "accept"}
-
              class:negative-review={element.inner[2].verdict === "reject"}>
+
              class:positive-review={review.verdict === "accept"}
+
              class:negative-review={review.verdict === "reject"}>
              <div class="action-content">
                <Authorship
                  authorId={author}
                  authorAlias={review.author.alias}
                  authorAliasColor={aliasColorForVerdict(review.verdict)}>
-
                  {formatVerdict(revisionId, review.verdict)}
+
                  {formatVerdict(review.verdict)}
                </Authorship>
              </div>
            </div>
modified src/views/projects/Cob/TagInput.svelte
@@ -57,9 +57,6 @@
    gap: 0.5rem;
    margin-bottom: 1.25rem;
  }
-
  .metadata-section-empty {
-
    color: var(--color-foreground-5);
-
  }
  .tag {
    overflow: hidden;
    text-overflow: ellipsis;
@@ -102,7 +99,7 @@
        <div class="tag">{tag}</div>
      </Chip>
    {:else}
-
      <div class="metadata-section-empty">No tags</div>
+
      <div class="txt-missing">No tags</div>
    {/each}
  </div>
  {#if editInProgress || action === "create"}
modified src/views/projects/Issue/IssueTeaser.svelte
@@ -11,18 +11,12 @@

  export let issue: Issue;

-
  const commentCount = countComments(issue);
-

-
  // Counts the amount of comments in a discussion, excluding the initial
-
  // description.
-
  function countComments(issue: Issue): number {
-
    return issue.discussion.reduce((acc, _curr, index) => {
-
      if (index !== 0) {
-
        return acc + 1;
-
      }
-
      return acc;
-
    }, 0);
-
  }
+
  $: commentCount = issue.discussion.reduce((acc, _curr, index) => {
+
    if (index !== 0) {
+
      return acc + 1;
+
    }
+
    return acc;
+
  }, 0);
</script>

<style>
modified src/views/projects/Patch.svelte
@@ -7,7 +7,7 @@
  }

  interface TimelineReview {
-
    inner: [string, string, Review];
+
    inner: [string, Review];
    type: "review";
    timestamp: number;
  }
@@ -139,6 +139,21 @@
    disabled: false,
  }));

+
  function computeReviews(patch: Patch) {
+
    const patchReviews: Record<string, { latest: boolean; review: Review }> =
+
      {};
+

+
    patch.revisions.forEach((rev, i) => {
+
      const latest = i === patch.revisions.length - 1;
+
      for (const review of rev.reviews) {
+
        patchReviews[review.author.id] = { latest, review };
+
      }
+
    });
+

+
    return patchReviews;
+
  }
+

+
  $: patchReviews = computeReviews(patch);
  $: currentRevision =
    (revision && patch.revisions.find(r => r.id === revision)) ||
    patch.revisions[patch.revisions.length - 1];
@@ -163,7 +178,7 @@
      ...rev.reviews.map<TimelineReview>(review => ({
        timestamp: review.timestamp,
        type: "review",
-
        inner: [rev.id, review.author.id, review],
+
        inner: [review.author.id, review],
      })),
      ...patch.merges
        .filter(merge => merge.revision === rev.id)
@@ -239,6 +254,28 @@
  .merged {
    color: var(--color-primary-6);
  }
+
  .metadata-section-header {
+
    font-size: var(--font-size-small);
+
    margin-bottom: 0.75rem;
+
    color: var(--color-foreground-6);
+
  }
+
  .metadata-section-body {
+
    display: flex;
+
    flex-direction: column;
+
    gap: 0.5rem;
+
    margin-bottom: 1.25rem;
+
  }
+
  .review {
+
    display: inline-flex;
+
    align-items: center;
+
    gap: 0.5rem;
+
  }
+
  .review-accept {
+
    color: var(--color-positive);
+
  }
+
  .review-reject {
+
    color: var(--color-negative);
+
  }

  @media (max-width: 1092px) {
    .patch {
@@ -425,6 +462,31 @@
    {/if}
  </div>
  <div class="metadata">
+
    <div>
+
      <div class="metadata-section-header">Reviews</div>
+
      <div class="metadata-section-body">
+
        {#each Object.values(patchReviews) as { latest, review }}
+
          <div class="review" class:txt-missing={!latest}>
+
            <span
+
              class:review-accept={review.verdict === "accept"}
+
              class:review-reject={review.verdict === "reject"}>
+
              {#if review.verdict === "accept"}
+
                <Icon size="small" name="checkmark" />
+
              {:else if review.verdict === "reject"}
+
                <Icon size="small" name="cross" />
+
              {:else}
+
                <Icon size="small" name="chat" />
+
              {/if}
+
            </span>
+
            <Authorship
+
              authorId={review.author.id}
+
              authorAlias={review.author.alias} />
+
          </div>
+
        {:else}
+
          <div class="txt-missing">No reviews</div>
+
        {/each}
+
      </div>
+
    </div>
    <TagInput {action} tags={patch.tags} on:save={saveTags} />
  </div>
</div>
modified src/views/projects/Patch/PatchTeaser.svelte
@@ -25,6 +25,12 @@
    latestRevision.base,
    latestRevision.oid,
  );
+

+
  // Counts the amount of comments in all the discussions over all revisions.
+
  $: commentCount = patch.revisions.reduce(
+
    (acc, curr) => acc + curr.discussions.reduce(acc => acc + 1, 0),
+
    0,
+
  );
</script>

<style>
@@ -156,12 +162,10 @@
    </div>
  </div>
  <div class="right">
-
    {#if latestRevision.discussions.length > 0}
+
    {#if commentCount > 0}
      <div class="comments">
        <Icon name="chat" />
-
        <span>
-
          {latestRevision.discussions.length}
-
        </span>
+
        <span>{commentCount}</span>
      </div>
    {/if}
    {#await diffPromise then { diff }}
modified tests/e2e/project/patches.spec.ts
@@ -7,6 +7,9 @@ test("navigate listing", async ({ page }) => {

  await page.locator('role=link[name="1 merged"]').click();
  await expect(page).toHaveURL(`${cobUrl}/patches?state=merged`);
+
  await expect(
+
    page.locator(".comments").filter({ hasText: "2" }),
+
  ).toBeVisible();
});

test("navigate patch details", async ({ page }) => {
modified tests/support/fixtures.ts
@@ -322,7 +322,7 @@ export async function createCobsFixture(peer: RadiclePeer) {
    ["assign", issueOne, "--to", `did:key:${peer.nodeId}`],
    createOptions(projectFolder, 1),
  );
-
  await peer.rad(
+
  const { stdout: commentIssueOne } = await peer.rad(
    [
      "comment",
      issueOne,
@@ -338,7 +338,7 @@ export async function createCobsFixture(peer: RadiclePeer) {
      "--message",
      "This is a reply, to a first comment.",
      "--reply-to",
-
      "a299a997301eae6528cb9f6fbdb8fac2cb4c3df0",
+
      commentIssueOne,
    ],
    createOptions(projectFolder, 3),
  );
@@ -384,7 +384,7 @@ export async function createCobsFixture(peer: RadiclePeer) {
    ["Let's add a README", "This repo needed a README"],
    { cwd: projectFolder },
  );
-
  await peer.rad(
+
  const { stdout: commentPatchOne } = await peer.rad(
    ["comment", patchOne, "--message", "I'll review the patch"],
    createOptions(projectFolder, 1),
  );
@@ -395,7 +395,7 @@ export async function createCobsFixture(peer: RadiclePeer) {
      "--message",
      "Thanks for that!",
      "--reply-to",
-
      "fa9e1d3d0d064449a2415072fe2d4eef28a2f603",
+
      commentPatchOne,
    ],
    createOptions(projectFolder, 2),
  );
modified tests/visual/cob.spec.ts
@@ -75,6 +75,11 @@ test("patch page", async ({ page }) => {
    { waitUntil: "networkidle" },
  );
  await expect(page).toHaveScreenshot({ fullPage: true });
+
  await page.goto(
+
    `${cobUrl}/patches/013f8b2734df1840b2e33d52ff5632c8d66b199a`,
+
    { waitUntil: "networkidle" },
+
  );
+
  await expect(page).toHaveScreenshot({ fullPage: true });
  // Open patch
  await page.goto(
    `${cobUrl}/patches/0f3697fed2743549e3bf531e9fa81284a6de1466`,