Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Consolidate and optimize data fetching for ProjectCard
Thomas Scholtes committed 2 years ago
commit 67e16ea7dd7151a5aa48486c64c543be37082c39
parent d315380e01e3a39f0cfa9509469ae8923e8d9f85
7 files changed +94 -147
modified src/components/ProjectCard.svelte
@@ -1,32 +1,25 @@
<script lang="ts">
-
  import type { BaseUrl } from "@httpd-client";
-

-
  import type { WeeklyActivity } from "@app/lib/commit";
  import { formatTimestamp, twemoji } from "@app/lib/utils";

  import ActivityDiagram from "@app/components/ActivityDiagram.svelte";
  import IconSmall from "@app/components/IconSmall.svelte";
  import Link from "@app/components/Link.svelte";

-
  export let compact = false;
+
  import type { ProjectInfo } from "./ProjectCard";

-
  export let activity: WeeklyActivity[];
-
  export let description: string;
-
  export let baseUrl: BaseUrl;
+
  export let compact = false;

-
  export let numberOfIssues: number;
-
  export let numberOfPatches: number;
+
  export let projectInfo: ProjectInfo;

  export let isDelegate: boolean;
  export let isSeeding: boolean;
-
  export let isPrivate: boolean;
-

-
  export let lastUpdatedTimestamp: number;
-

-
  $: lastUpdated = formatTimestamp(lastUpdatedTimestamp);

-
  export let id: string;
-
  export let name: string;
+
  $: project = projectInfo.project;
+
  $: baseUrl = projectInfo.baseUrl;
+
  $: isPrivate = project.visibility?.type === "private";
+
  $: lastUpdated = formatTimestamp(
+
    projectInfo.lastCommit.commit.committer.time,
+
  );
</script>

<style>
@@ -138,21 +131,21 @@
<Link
  route={{
    resource: "project.source",
-
    project: id,
+
    project: project.id,
    node: baseUrl,
  }}>
  <div class="project-card" class:compact>
    <div class="activity">
      <div class="fadeout-overlay" />
      <ActivityDiagram
-
        {id}
+
        id={project.id}
        viewBoxHeight={200}
        styleColor="var(--color-foreground-primary"
-
        {activity} />
+
        activity={projectInfo.activity} />
    </div>
    <div class="title">
      <div class="headline-and-badges">
-
        <h4 use:twemoji>{name}</h4>
+
        <h4 use:twemoji>{project.name}</h4>
        <div class="badges">
          {#if isPrivate}
            <div
@@ -180,14 +173,14 @@
          {/if}
        </div>
      </div>
-
      <p class="txt-small" use:twemoji>{description}</p>
+
      <p class="txt-small" use:twemoji>{project.description}</p>
    </div>
    <div class="stats-row txt-tiny" style:color="var(--color-foreground-dim)">
      <IconSmall name="issue" />
-
      {numberOfIssues} ·
+
      {project.issues.open} ·
      <IconSmall name="patch" />
      <span style:overflow="hidden" style:text-overflow="ellipsis">
-
        {numberOfPatches} · Updated {lastUpdated}
+
        {project.patches.open} · Updated {lastUpdated}
      </span>
    </div>
  </div>
added src/components/ProjectCard.ts
@@ -0,0 +1,38 @@
+
import { loadProjectActivity, type WeeklyActivity } from "@app/lib/commit";
+
import {
+
  HttpdClient,
+
  type BaseUrl,
+
  type Commit,
+
  type Project,
+
} from "@httpd-client";
+

+
export interface ProjectInfo {
+
  project: Project;
+
  baseUrl: BaseUrl;
+
  activity: WeeklyActivity[];
+
  lastCommit: Commit;
+
}
+

+
export async function fetchProjectInfos(
+
  baseUrl: BaseUrl,
+
  show: "all" | "pinned",
+
): Promise<ProjectInfo[]> {
+
  const api = new HttpdClient(baseUrl);
+
  const projects = await api.project.getAll({ show });
+
  const info = await Promise.all(
+
    projects.map(async project => {
+
      const [activity, lastCommit] = await Promise.all([
+
        loadProjectActivity(project.id, baseUrl),
+
        api.project.getCommitBySha(project.id, project.head),
+
      ]);
+
      return { project, activity, lastCommit, baseUrl };
+
    }),
+
  );
+

+
  return info.sort((a, b) => {
+
    const aLastCommit = a.lastCommit.commit.committer.time;
+
    const bLastCommit = b.lastCommit.commit.committer.time;
+

+
    return bLastCommit - aLastCommit;
+
  });
+
}
modified src/lib/commit.ts
@@ -116,10 +116,3 @@ export async function loadProjectActivity(id: string, baseUrl: BaseUrl) {

  return groupCommitsByWeek(commits.activity);
}
-

-
export async function fetchLastCommit(id: string, baseUrl: BaseUrl) {
-
  const api = new HttpdClient(baseUrl);
-
  const res = await api.project.getAllCommits(id, { perPage: 1 });
-

-
  return res.commits[0];
-
}
modified src/lib/projects.ts
@@ -2,22 +2,12 @@ import type { BaseUrl, Project } from "@httpd-client";

import { HttpdClient } from "@httpd-client";
import { isFulfilled } from "@app/lib/utils";
-
import {
-
  fetchLastCommit,
-
  loadProjectActivity,
-
  type WeeklyActivity,
-
} from "./commit";

export interface ProjectBaseUrl {
  project: Project;
  baseUrl: BaseUrl;
}

-
export interface ProjectWithListingData extends ProjectBaseUrl {
-
  activity: WeeklyActivity[];
-
  lastCommit: Awaited<ReturnType<typeof fetchLastCommit>>;
-
}
-

export async function getProjectsFromNodes(
  params: { id: string; baseUrl: BaseUrl }[],
): Promise<ProjectBaseUrl[]> {
@@ -34,32 +24,6 @@ export async function getProjectsFromNodes(
  return results.filter(isFulfilled).map(r => r.value);
}

-
export async function getProjectListingData(id: string, baseUrl: BaseUrl) {
-
  const activity = await loadProjectActivity(id, baseUrl);
-
  const lastCommit = await fetchLastCommit(id, baseUrl);
-

-
  return { activity, lastCommit };
-
}
-

-
export async function getProjectsListingData(projects: ProjectBaseUrl[]) {
-
  const result = await Promise.all(
-
    projects.map(async ({ project, baseUrl }) => {
-
      const { activity, lastCommit } = await getProjectListingData(
-
        project.id,
-
        baseUrl,
-
      );
-
      return { project, activity, lastCommit, baseUrl };
-
    }),
-
  );
-

-
  return result.sort((a, b) => {
-
    const aLastCommit = a.lastCommit?.commit.committer.time ?? 0;
-
    const bLastCommit = b.lastCommit?.commit.committer.time ?? 0;
-

-
    return bLastCommit - aLastCommit;
-
  });
-
}
-

export async function queryProject(
  baseUrl: BaseUrl,
  projectId: string,
modified src/views/home/Index.svelte
@@ -1,17 +1,13 @@
<script lang="ts">
-
  import type { BaseUrl } from "@httpd-client";
-
  import type { ProjectWithListingData } from "@app/lib/projects";
  import type { ComponentProps } from "svelte";

  import storedWritable from "@efstajas/svelte-stored-writable";
-
  import { HttpdClient } from "@httpd-client";
  import { derived } from "svelte/store";
  import { literal, union } from "zod";

  import { api, httpdStore } from "@app/lib/httpd";
  import { deduplicateStore } from "@app/lib/deduplicateStore";
  import { baseUrlToString } from "@app/lib/utils";
-
  import { getProjectsListingData } from "@app/lib/projects";
  import { handleError } from "@app/views/home/error";
  import { isDelegate } from "@app/lib/roles";
  import { preferredSeeds } from "@app/lib/seeds";
@@ -25,6 +21,10 @@
  import HomepageSection from "./components/HomepageSection.svelte";
  import NewProjectButton from "./components/NewProjectButton.svelte";
  import PreferredSeedDropdown from "./components/PreferredSeedDropdown.svelte";
+
  import {
+
    fetchProjectInfos,
+
    type ProjectInfo,
+
  } from "@app/components/ProjectCard";

  const selectedSeed = deduplicateStore(
    derived(preferredSeeds, $ => $?.selected),
@@ -40,30 +40,17 @@
  );

  let localProjects:
-
    | ProjectWithListingData[]
+
    | ProjectInfo[]
    | ComponentProps<ErrorMessage>["error"]
    | undefined;
  let preferredSeedProjects:
-
    | ProjectWithListingData[]
+
    | ProjectInfo[]
    | ComponentProps<ErrorMessage>["error"]
    | undefined;

-
  async function fetchProjects(baseUrl: BaseUrl, show: "all" | "pinned") {
-
    const api = new HttpdClient(baseUrl);
-

-
    const projects = (await api.project.getAll({ perPage: 30, show })).map(
-
      project => ({
-
        project,
-
        baseUrl,
-
      }),
-
    );
-

-
    return await getProjectsListingData(projects);
-
  }
-

  async function loadLocalProjects() {
    localProjects = undefined;
-
    localProjects = await fetchProjects(api.baseUrl, "all").catch(
+
    localProjects = await fetchProjectInfos(api.baseUrl, "all").catch(
      error => error,
    );
  }
@@ -72,9 +59,10 @@
    preferredSeedProjects = undefined;

    if (!$selectedSeed) return;
-
    preferredSeedProjects = await fetchProjects($selectedSeed, "pinned").catch(
-
      error => error,
-
    );
+
    preferredSeedProjects = await fetchProjectInfos(
+
      $selectedSeed,
+
      "pinned",
+
    ).catch(error => error);
  }

  function isSeeding(projectId: string) {
@@ -176,19 +164,12 @@
        </svelte:fragment>
        <div class="project-grid">
          {#if filteredLocalProjects && !(filteredLocalProjects instanceof Error)}
-
            {#each filteredLocalProjects as { project, baseUrl, activity, lastCommit }}
+
            {#each filteredLocalProjects as projectInfo}
              <ProjectCard
-
                id={project.id}
-
                name={project.name}
-
                description={project.description}
-
                numberOfIssues={project.issues.open}
-
                numberOfPatches={project.patches.open}
-
                isPrivate={project.visibility?.type === "private"}
+
                {projectInfo}
                isSeeding={true}
-
                isDelegate={isDelegate(nodeId, project.delegates) ?? false}
-
                lastUpdatedTimestamp={lastCommit.commit.committer.time}
-
                {activity}
-
                {baseUrl} />
+
                isDelegate={isDelegate(nodeId, projectInfo.project.delegates) ??
+
                  false} />
            {/each}
          {/if}
        </div>
@@ -228,19 +209,12 @@
      </svelte:fragment>
      <div class="project-grid">
        {#if preferredSeedProjects && !(preferredSeedProjects instanceof Error)}
-
          {#each preferredSeedProjects as { project, baseUrl, activity, lastCommit }}
+
          {#each preferredSeedProjects as projectInfo}
            <ProjectCard
-
              id={project.id}
-
              name={project.name}
-
              description={project.description}
-
              numberOfIssues={project.issues.open}
-
              numberOfPatches={project.patches.open}
-
              isPrivate={project.visibility?.type === "private"}
-
              isSeeding={isSeeding(project.id)}
-
              isDelegate={isDelegate(nodeId, project.delegates) ?? false}
-
              lastUpdatedTimestamp={lastCommit.commit.committer.time}
-
              {activity}
-
              {baseUrl} />
+
              {projectInfo}
+
              isSeeding={isSeeding(projectInfo.project.id)}
+
              isDelegate={isDelegate(nodeId, projectInfo.project.delegates) ??
+
                false} />
          {/each}
        {/if}
      </div>
modified src/views/nodes/View.svelte
@@ -1,6 +1,5 @@
<script lang="ts">
  import type { BaseUrl, Policy, Scope } from "@httpd-client";
-
  import type { ProjectWithListingData } from "@app/lib/projects";

  import capitalize from "lodash/capitalize";

@@ -14,11 +13,12 @@
  import ProjectCard from "@app/components/ProjectCard.svelte";
  import ScopePolicyExplainer from "@app/components/ScopePolicyExplainer.svelte";
  import HoverPopover from "@app/components/HoverPopover.svelte";
+
  import type { ProjectInfo } from "@app/components/ProjectCard";

  export let baseUrl: BaseUrl;
  export let nid: string;
  export let externalAddresses: string[];
-
  export let projects: ProjectWithListingData[] = [];
+
  export let projectInfos: ProjectInfo[];
  export let version: string;
  export let policy: Policy | undefined = undefined;
  export let scope: Scope | undefined = undefined;
@@ -135,7 +135,7 @@

      <div class="subtitle">
        <div class="pinned txt-semibold">
-
          {projects.length}
+
          {projectInfos.length}
          {isLocal(baseUrl.hostname) ? "" : "pinned"} projects
        </div>

@@ -188,20 +188,14 @@
      </div>

      <div class="project-grid">
-
        {#each projects as { project, baseUrl, activity, lastCommit }}
+
        {#each projectInfos as projectInfo}
          <ProjectCard
-
            id={project.id}
-
            name={project.name}
-
            description={project.description}
-
            numberOfIssues={project.issues.open}
-
            numberOfPatches={project.patches.open}
-
            isPrivate={project.visibility?.type === "private"}
+
            {projectInfo}
            isSeeding={false}
-
            isDelegate={isDelegate(session?.publicKey, project.delegates) ??
-
              false}
-
            lastUpdatedTimestamp={lastCommit.commit.committer.time}
-
            {activity}
-
            {baseUrl} />
+
            isDelegate={isDelegate(
+
              session?.publicKey,
+
              projectInfo.project.delegates,
+
            ) ?? false} />
        {/each}
      </div>
    </div>
modified src/views/nodes/router.ts
@@ -1,12 +1,14 @@
import type { BaseUrl, Policy, Scope } from "@httpd-client";
import type { ErrorRoute, NotFoundRoute } from "@app/lib/router/definitions";
-
import type { ProjectWithListingData } from "@app/lib/projects";

import { HttpdClient } from "@httpd-client";
import { config } from "@app/lib/config";
import { baseUrlToString, isLocal } from "@app/lib/utils";
-
import { getProjectsListingData } from "@app/lib/projects";
import { handleError } from "@app/views/nodes/error";
+
import {
+
  fetchProjectInfos,
+
  type ProjectInfo,
+
} from "@app/components/ProjectCard";

export interface NodesRouteParams {
  baseUrl: BaseUrl;
@@ -25,26 +27,12 @@ export interface NodesLoadedRoute {
    version: string;
    externalAddresses: string[];
    nid: string;
-
    projects: ProjectWithListingData[];
+
    projectInfos: ProjectInfo[];
    policy?: Policy;
    scope?: Scope;
  };
}

-
async function loadProjects(
-
  baseUrl: BaseUrl,
-
): Promise<ProjectWithListingData[]> {
-
  const api = new HttpdClient(baseUrl);
-
  const projects = await api.project.getAll({
-
    show: isLocal(baseUrl.hostname) ? "all" : "pinned",
-
  });
-
  const results = await getProjectsListingData(
-
    projects.map(p => ({ project: p, baseUrl })),
-
  );
-

-
  return results;
-
}
-

export function nodePath(baseUrl: BaseUrl) {
  const port = baseUrl.port ?? config.nodes.defaultHttpdPort;

@@ -60,9 +48,12 @@ export async function loadNodeRoute(
): Promise<NodesLoadedRoute | NotFoundRoute | ErrorRoute> {
  const api = new HttpdClient(params.baseUrl);
  try {
-
    const [node, projects] = await Promise.all([
+
    const [node, projectInfos] = await Promise.all([
      api.getNode(),
-
      loadProjects(params.baseUrl),
+
      fetchProjectInfos(
+
        params.baseUrl,
+
        isLocal(params.baseUrl.hostname) ? "all" : "pinned",
+
      ),
    ]);
    return {
      resource: "nodes",
@@ -71,7 +62,7 @@ export async function loadNodeRoute(
        nid: node.id,
        externalAddresses: node.config?.externalAddresses ?? [],
        version: node.version,
-
        projects: projects,
+
        projectInfos: projectInfos,
        policy: node.config?.policy,
        scope: node.config?.scope,
      },