]> git.ipfire.org Git - pbs.git/commitdiff
frontend: Fetch bugs that belong to a build
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 18 Jul 2025 10:49:19 +0000 (10:49 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 18 Jul 2025 10:49:19 +0000 (10:49 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
frontend/src/api/bugs.ts [new file with mode: 0644]
frontend/src/api/builds.ts
frontend/src/components/BugList.vue [new file with mode: 0644]
frontend/src/components/BuildBugs.vue [new file with mode: 0644]
frontend/src/composables/builds.ts
frontend/src/utils/format.ts
frontend/src/views/BuildView.vue

diff --git a/frontend/src/api/bugs.ts b/frontend/src/api/bugs.ts
new file mode 100644 (file)
index 0000000..f1808ca
--- /dev/null
@@ -0,0 +1,28 @@
+export interface Bug {
+       // ID
+       id: number;
+
+       // Summary
+       summary: string;
+
+       // URL
+       url: string;
+
+       // Created At
+       created_at: string;
+
+       // Severity
+       severity: string;
+
+       // Component
+       component: string;
+
+       // Status
+       status: string;
+
+       // Resolution
+       resolution: string;
+
+       // Keywords
+       keywords: string[];
+}
index f54455520453943c56b029c50ed3f3966a130586..12d64ad22247c9b0b967f228830946b613db4b11 100644 (file)
@@ -1,4 +1,5 @@
-import api from "@/api"
+import api from "@/api";
+import type { Bug } from "@/api/bugs";
 
 export interface Build {
        // Name
@@ -22,3 +23,9 @@ export async function fetchBuild(uuid: string): Promise<Build> {
        const response = await api.get(`/v1/builds/${uuid}`);
        return response.data as Build;
 }
+
+// Fetch bugs by the build UUID
+export async function fetchBuildBugs(uuid: string): Promise<Bug[]> {
+       const response = await api.get(`/v1/builds/${uuid}/bugs`);
+       return response.data as Bug[];
+}
diff --git a/frontend/src/components/BugList.vue b/frontend/src/components/BugList.vue
new file mode 100644 (file)
index 0000000..3693060
--- /dev/null
@@ -0,0 +1,56 @@
+<script setup lang="ts">
+       import type { Bug } from "@/api/bugs";
+       import { formatTimeRelativeFromString } from "@/utils/format";
+
+       // Fetch the build
+       defineProps<{
+               bugs: Bug[],
+       }>();
+</script>
+
+<template>
+       <article class="media" v-for="bug in bugs" :key="bug.id">
+               <div class="media-left">
+                       <p class="image is-48x48">
+                               AVATAR
+                       </p>
+               </div>
+
+               <div class="media-content">
+                       <p>
+                               <a href="{{ bug.url }}">
+                                       #{{ bug.id }}
+                               </a>
+
+                               &dash;
+
+                               <strong>
+                                       {{ bug.summary }}
+                               </strong>
+
+                               <small>
+                                       CREATOR
+                               </small>
+
+                               <small>
+                                       {{ formatTimeRelativeFromString(bug.created_at) }}
+                               </small>
+
+                               <br>
+
+                               <!-- Status -->
+                               {{ bug.status }}
+
+                               <!-- Resolution -->
+                               <span v-if="bug.resolution">
+                                       {{ bug.resolution }}
+                               </span>
+
+                               <!--
+                               {% if assignee %}
+                                       &dash; ASSIGNEE
+                               {% endif %} -->
+                       </p>
+               </div>
+       </article>
+</template>
diff --git a/frontend/src/components/BuildBugs.vue b/frontend/src/components/BuildBugs.vue
new file mode 100644 (file)
index 0000000..d542fb7
--- /dev/null
@@ -0,0 +1,32 @@
+<script setup lang="ts">
+       import { ref, onMounted } from "vue";
+
+       // Composables
+       import type { Bug } from "@/api/bugs";
+       import { useBuild } from "@/composables/builds";
+
+       // Import UI components
+       import BugList from "@/components/BugList.vue";
+
+       // Fetch the build UUID
+       const props = defineProps<{
+               uuid: string,
+       }>();
+
+       const bugs = ref<Bug[] | null>(null);
+
+       // Fetch the build
+       const { getBugs } = useBuild(props.uuid);
+
+       onMounted(async () => {
+               bugs.value = await getBugs();
+
+               console.log(bugs.value);
+       });
+</script>
+
+<template>
+       <Section :title="$t('Fixed Bugs')">
+               <BugList :bugs="bugs" v-if="bugs" />
+       </Section>
+</template>
index 587b74445cf85efa3cab25dd8fe0cf487fa39eb4..ed1748964d4c07d24738374cb6208512c22342fb 100644 (file)
@@ -1,9 +1,11 @@
 import { ref } from "vue";
 
 // API
+import type { Bug } from "@/api/bugs";
 import type { Build } from "@/api/builds";
 import {
        fetchBuild,
+       fetchBuildBugs,
 } from "@/api/builds";
 
 export function useBuild(uuid: string) {
@@ -14,8 +16,14 @@ export function useBuild(uuid: string) {
                build.value = await fetchBuild(uuid);
        }
 
+       // Fetch all bugs
+       async function getBugs(): Promise<Bug[]> {
+               return await fetchBuildBugs(uuid);
+       }
+
        return {
                build,
                loadBuild,
+               getBugs,
        };
 }
index dd9125a22845cfb4c866bd4c37cffa3637ed1b87..be4507b0b3bfeda55c486c577c51f06e39b9946a 100644 (file)
@@ -1,3 +1,46 @@
+const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" })
+
+// Function to format "x minutes ago"
+export function formatTimeRelative(t: Date): string {
+       // Fetch the current time
+       const now = Date.now();
+
+       // Work out the time that has passed in seconds
+       const diff = Math.floor((t.getTime() - now) / 1000);
+
+       const ranges: [number, Intl.RelativeTimeFormatUnit][] = [
+               // Up to 60s → seconds
+               [60, "second"],
+               // Up to 1h → minutes
+               [3600, "minute"],
+               // Up to 1d → hours
+               [86400, "hour"],
+               // Up to 7d → days
+               [604800, "day"],
+               // Up to ~1mo → weeks
+               [2592000, "week"],
+               // Up to 1y → months
+               [31536000, "month"],
+               // Beyond → years
+               [Infinity, "year"],
+       ];
+
+       for (const [limit, unit] of ranges) {
+               if (Math.abs(diff) < limit) {
+                       const value = Math.round(diff / (limit / 60));
+
+                       return rtf.format(value, unit);
+               }
+       }
+
+       // We will never get here, but we need to make the compiler happy
+       return "";
+}
+
+export function formatTimeRelativeFromString(t: string): string {
+       return formatTimeRelative(new Date(t));
+}
+
 export function formatSize(bytes: number, decimals = 1): string {
        const base = 1024;
 
index 5318a6f69658df616cbe527fb1bb141c6b3747ab..35caa3c9fecbf5dd4787edbb2f1ce6fe249a8b7b 100644 (file)
@@ -7,6 +7,7 @@
 
        // Import UI components
        import BuildHeader from "@/components/BuildHeader.vue";
+       import BuildBugs from "@/components/BuildBugs.vue";
 
        // Fetch the build UUID from the URL
        const { uuid } = defineProps<{
@@ -14,7 +15,7 @@
        }>()
 
        // Fetch the build
-       const { build, loadBuild } = useBuild(uuid);
+       const { build, loadBuild, getBugs } = useBuild(uuid);
 
        onMounted(async () => {
                await loadBuild();
@@ -24,4 +25,7 @@
 <template>
        <!-- Show the header -->
        <BuildHeader v-if="build" :build="build" />
+
+       <!-- Show a list of all bugs -->
+       <BuildBugs :uuid="uuid" />
 </template>