--- /dev/null
+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[];
+}
-import api from "@/api"
+import api from "@/api";
+import type { Bug } from "@/api/bugs";
export interface Build {
// Name
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[];
+}
--- /dev/null
+<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>
+
+ ‐
+
+ <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 %}
+ ‐ ASSIGNEE
+ {% endif %} -->
+ </p>
+ </div>
+ </article>
+</template>
--- /dev/null
+<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>
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) {
build.value = await fetchBuild(uuid);
}
+ // Fetch all bugs
+ async function getBugs(): Promise<Bug[]> {
+ return await fetchBuildBugs(uuid);
+ }
+
return {
build,
loadBuild,
+ getBugs,
};
}
+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;
// 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<{
}>()
// Fetch the build
- const { build, loadBuild } = useBuild(uuid);
+ const { build, loadBuild, getBugs } = useBuild(uuid);
onMounted(async () => {
await loadBuild();
<template>
<!-- Show the header -->
<BuildHeader v-if="build" :build="build" />
+
+ <!-- Show a list of all bugs -->
+ <BuildBugs :uuid="uuid" />
</template>