Fetch cobs by selected or default state

Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
Sebastian Martinez
2023-04-20 22:29:37 +02:00
parent 9704318248
commit d77b6041a6
4 changed files with 200 additions and 122 deletions
+12
View File
@@ -370,12 +370,18 @@ export class Client {
public async getAllIssues(
id: string,
query?: {
page?: number;
perPage?: number;
state?: string;
},
options?: RequestOptions,
): Promise<Issue[]> {
return this.#fetcher.fetchOk(
{
method: "GET",
path: `projects/${id}/issues`,
query,
options,
},
issuesSchema,
@@ -441,12 +447,18 @@ export class Client {
public async getAllPatches(
id: string,
query?: {
page?: number;
perPage?: number;
state?: string;
},
options?: RequestOptions,
): Promise<Patch[]> {
return this.#fetcher.fetchOk(
{
method: "GET",
path: `projects/${id}/patches`,
query,
options,
},
patchesSchema,
+87 -42
View File
@@ -12,48 +12,79 @@
import * as router from "@app/lib/router";
import * as utils from "@app/lib/utils";
import capitalize from "lodash/capitalize";
import { HttpdClient } from "@httpd-client";
import { sessionStore } from "@app/lib/session";
import HeaderToggleLabel from "@app/views/projects/HeaderToggleLabel.svelte";
import IssueTeaser from "@app/views/projects/Issue/IssueTeaser.svelte";
import Placeholder from "@app/components/Placeholder.svelte";
import TabBar from "@app/components/TabBar.svelte";
import Loading from "@app/components/Loading.svelte";
import Button from "@app/components/Button.svelte";
export let issues: Issue[];
export let status: IssueStatus;
export let projectId: string;
export let state: IssueStatus;
export let baseUrl: BaseUrl;
export let issueCounters: { open: number; closed: number };
let options: Tab<IssueStatus>[];
const perPage = 10;
function groupIssues(issues: Issue[]): {
open: Issue[];
closed: Issue[];
} {
return issues.reduce(
(acc, issue) => {
acc[issue.state.status].push(issue);
return acc;
},
{ open: [] as Issue[], closed: [] as Issue[] },
);
// Keeping it true, to avoid an initial flash
// of EmptyState Placeholder
let refresh = true;
let loading = false;
let page = 0;
let error: any;
let issues: Issue[] = [];
const api = new HttpdClient(baseUrl);
async function loadIssues(): Promise<void> {
loading = true;
try {
const response = await api.project.getAllIssues(projectId, {
state,
page,
perPage,
});
issues = [...issues, ...response];
page += 1;
} catch (e) {
error = e;
} finally {
loading = false;
refresh = false;
}
}
function switchState(e: CustomEvent<IssueStatus>): void {
refresh = true;
// Update state to be used in the query
state = e.detail;
// Reset page to 0 to load the first page for a new state
page = 0;
// Remove all existing patches with old state
issues = [];
loadIssues();
router.updateProjectRoute({
search: `state=${state}`,
});
}
const stateOptions: IssueStatus[] = ["open", "closed"];
$: options = stateOptions.map<{
value: IssueStatus;
title: string;
disabled: boolean;
}>((s: IssueStatus) => ({
const options = stateOptions.map<Tab<IssueStatus>>(s => ({
value: s,
title: `${issueCounters[s]} ${s}`,
disabled: issueCounters[s] === 0,
}));
$: filteredIssues = groupIssues(issues)[status];
$: sortedIssues = filteredIssues.sort(
({ discussion: t1 }, { discussion: t2 }) =>
t2[0].timestamp - t1[0].timestamp,
);
$: showMoreButton =
!loading &&
!error &&
issueCounters[state] &&
issues.length < issueCounters[state];
loadIssues();
</script>
<style>
@@ -74,6 +105,14 @@
justify-content: space-between;
width: 100%;
}
.loader {
margin-top: 8rem;
}
.more {
margin-top: 2rem;
text-align: center;
min-height: 3rem;
}
@media (max-width: 960px) {
.issues {
@@ -85,13 +124,7 @@
<div class="issues">
<div class="section-header">
<div style="margin-bottom: 1rem;">
<TabBar
{options}
on:select={e =>
router.updateProjectRoute({
search: `state=${e.detail}`,
})}
active={status} />
<TabBar {options} on:select={switchState} active={state} />
</div>
<HeaderToggleLabel
disabled={!$sessionStore || !utils.isLocal(baseUrl.hostname)}
@@ -107,10 +140,13 @@
New issue
</HeaderToggleLabel>
</div>
{#if filteredIssues.length}
<div class="issues-list">
{#each sortedIssues as issue}
<div class="issues-list">
{#if refresh}
<div class="loader">
<Loading center />
</div>
{:else}
{#each issues as issue}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="teaser"
@@ -124,12 +160,21 @@
}}>
<IssueTeaser {issue} />
</div>
{:else}
<Placeholder emoji="🍂">
<div slot="title">{capitalize(state)} issues</div>
<div slot="body">No issues matched the current filter</div>
</Placeholder>
{/each}
</div>
{:else}
<Placeholder emoji="🍂">
<div slot="title">{capitalize(status)} issues</div>
<div slot="body">No issues matched the current filter</div>
</Placeholder>
{/if}
<div class="more">
{#if loading}
<Loading small={page !== 0} center />
{/if}
{#if showMoreButton}
<Button variant="foreground" on:click={loadIssues}>More</Button>
{/if}
</div>
{/if}
</div>
</div>
+91 -53
View File
@@ -1,67 +1,90 @@
<script lang="ts" context="module">
import type { PatchState } from "@httpd-client";
import type { Patch, PatchState } from "@httpd-client";
export type PatchStatus = PatchState["status"];
</script>
<script lang="ts">
import type { Patch } from "@httpd-client";
import type { Tab } from "@app/components/TabBar.svelte";
import type { BaseUrl } from "@httpd-client";
import * as router from "@app/lib/router";
import { HttpdClient } from "@httpd-client";
import PatchTeaser from "./Patch/PatchTeaser.svelte";
import Placeholder from "@app/components/Placeholder.svelte";
import capitalize from "lodash/capitalize";
import TabBar from "@app/components/TabBar.svelte";
import Loading from "@app/components/Loading.svelte";
import Button from "@app/components/Button.svelte";
export let patches: Patch[];
export let status: PatchStatus;
export let baseUrl: BaseUrl;
export let projectId: string;
export let projectPatches: {
export let state: PatchStatus;
export let baseUrl: BaseUrl;
export let patchCounters: {
draft: number;
open: number;
archived: number;
merged: number;
};
let options: Tab<PatchStatus>[];
const perPage = 10;
function groupPatches(patches: Patch[]): {
open: Patch[];
draft: Patch[];
archived: Patch[];
merged: Patch[];
} {
return patches.reduce(
(acc, patch) => {
acc[patch.state.status].push(patch);
return acc;
},
{
open: [] as Patch[],
draft: [] as Patch[],
archived: [] as Patch[],
merged: [] as Patch[],
},
);
// Keeping it true, to avoid an initial flash
// of EmptyState Placeholder
let refresh = true;
let loading = false;
let page = 0;
let error: any;
let patches: Patch[] = [];
const api = new HttpdClient(baseUrl);
async function loadPatches(): Promise<void> {
loading = true;
try {
const response = await api.project.getAllPatches(projectId, {
state,
page,
perPage,
});
patches = [...patches, ...response];
page += 1;
} catch (e) {
error = e;
} finally {
loading = false;
refresh = false;
}
}
function switchState(e: CustomEvent<PatchStatus>): void {
refresh = true;
// Update state to be used in the query
state = e.detail;
// Reset page to 0 to load the first page for a new state
page = 0;
// Remove all existing patches with old state
patches = [];
loadPatches();
router.updateProjectRoute({
search: `state=${state}`,
});
}
const stateOptions: PatchStatus[] = ["draft", "open", "archived", "merged"];
$: options = stateOptions.map<{
value: PatchStatus;
title: string;
disabled: boolean;
}>((s: PatchStatus) => ({
const options = stateOptions.map<Tab<PatchStatus>>(s => ({
value: s,
title: `${projectPatches[s]} ${s}`,
disabled: projectPatches[s] === 0,
title: `${patchCounters[s]} ${s}`,
disabled: patchCounters[s] === 0,
}));
$: filteredPatches = groupPatches(patches)[status];
$: sortedPatches = filteredPatches.sort(
({ revisions: [r1] }, { revisions: [r2] }) => r2.timestamp - r1.timestamp,
);
$: showMoreButton =
!loading &&
!error &&
patchCounters[state] &&
patches.length < patchCounters[state];
loadPatches();
</script>
<style>
@@ -73,6 +96,14 @@
border-radius: var(--border-radius);
overflow: hidden;
}
.loader {
margin-top: 8rem;
}
.more {
margin-top: 2rem;
text-align: center;
min-height: 3rem;
}
.teaser:not(:last-child) {
border-bottom: 1px dashed var(--color-background);
}
@@ -86,17 +117,15 @@
<div class="patches">
<div style="margin-bottom: 1rem;">
<TabBar
{options}
on:select={e =>
router.updateProjectRoute({
search: `state=${e.detail}`,
})}
active={status} />
<TabBar {options} on:select={switchState} active={state} />
</div>
{#if filteredPatches.length}
<div class="patches-list">
{#each sortedPatches as patch}
<div class="patches-list">
{#if refresh}
<div class="loader">
<Loading center />
</div>
{:else}
{#each patches as patch}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="teaser"
@@ -110,12 +139,21 @@
}}>
<PatchTeaser {baseUrl} {projectId} {patch} />
</div>
{:else}
<Placeholder emoji="🍂">
<div slot="title">{capitalize(state)} patches</div>
<div slot="body">No patches matched the current filter</div>
</Placeholder>
{/each}
</div>
{:else}
<Placeholder emoji="🍂">
<div slot="title">{capitalize(status)} patches</div>
<div slot="body">No issues matched the current filter</div>
</Placeholder>
{/if}
<div class="more">
{#if loading}
<Loading small={page !== 0} center />
{/if}
{#if showMoreButton}
<Button variant="foreground" on:click={loadPatches}>More</Button>
{/if}
</div>
{/if}
</div>
</div>
+10 -27
View File
@@ -235,19 +235,11 @@
</div>
{/if}
{:else if activeRoute.params.view.resource === "issues"}
{#await api.project.getAllIssues(project.id)}
<Loading center />
{:then issues}
<Issues
{baseUrl}
issueCounters={project.issues}
status={issueFilter}
{issues} />
{:catch e}
<div class="message">
<ErrorMessage message="Couldn't load issues." stackTrace={e} />
</div>
{/await}
<Issues
{baseUrl}
projectId={project.id}
issueCounters={project.issues}
state={issueFilter} />
{:else if activeRoute.params.view.resource === "issue"}
{#await api.project.getIssueById(project.id, activeRoute.params.view.params.issue)}
<Loading center />
@@ -264,20 +256,11 @@
</div>
{/await}
{:else if activeRoute.params.view.resource === "patches"}
{#await api.project.getAllPatches(project.id)}
<Loading center />
{:then patches}
<Patches
{patches}
status={patchFilter}
projectId={project.id}
{baseUrl}
projectPatches={project.patches} />
{:catch e}
<div class="message">
<ErrorMessage message="Couldn't load patches." stackTrace={e} />
</div>
{/await}
<Patches
{baseUrl}
projectId={project.id}
state={patchFilter}
patchCounters={project.patches} />
{:else if activeRoute.params.view.resource === "patch"}
{#await api.project.getPatchById(project.id, activeRoute.params.view.params.patch)}
<Loading center />