Skip to content

Commit

Permalink
connect dashboard tree to the API
Browse files Browse the repository at this point in the history
  • Loading branch information
rouk1 committed Jul 16, 2024
1 parent 4e2526f commit 7e76b45
Show file tree
Hide file tree
Showing 16 changed files with 289 additions and 87 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ check-wip:
python -m pytest tests

serve-api:
python -m uvicorn mandr.dashboard.webapp:app --reload --reload-dir src --host 0.0.0.0 --timeout-graceful-shutdown 0
python -m uvicorn mandr.dashboard.webapp:app --reload --reload-dir ./src --host 0.0.0.0 --timeout-graceful-shutdown 0

build-frontend:
# build the SPA
Expand Down
16 changes: 11 additions & 5 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
<script setup lang="ts">
import { RouterView } from "vue-router";
import LoadingBars from "./components/LoadingBars.vue";
</script>

<template>
<header>
<nav></nav>
</header>

<RouterView />
<RouterView v-slot="{ Component }">
<template v-if="Component">
<Suspense>
<component :is="Component" />
<template #fallback>
<LoadingBars />
</template>
</Suspense>
</template>
</RouterView>
</template>
4 changes: 2 additions & 2 deletions frontend/src/components/FileTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ export interface FileTreeNode {
<script setup lang="ts">
import FileTreeItem from "./FileTreeItem.vue";
const props = defineProps<{ nodes: FileTreeNode[] }>();
defineProps<{ nodes: FileTreeNode[] }>();
</script>

<template>
<FileTreeItem
v-for="(node, index) in props.nodes"
v-for="(node, index) in nodes"
:key="index"
:label="node.label"
:children="node.children"
Expand Down
94 changes: 94 additions & 0 deletions frontend/src/components/LoadingBars.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="45" height="45" viewBox="0 0 135 140" fill="#ddd">
<rect y="10" width="15" height="120" rx="6">
<animate
attributeName="height"
begin="0.5s"
dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120"
calcMode="linear"
repeatCount="indefinite"
/>
<animate
attributeName="y"
begin="0.5s"
dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10"
calcMode="linear"
repeatCount="indefinite"
/>
</rect>
<rect x="30" y="10" width="15" height="120" rx="6">
<animate
attributeName="height"
begin="0.25s"
dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120"
calcMode="linear"
repeatCount="indefinite"
/>
<animate
attributeName="y"
begin="0.25s"
dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10"
calcMode="linear"
repeatCount="indefinite"
/>
</rect>
<rect x="60" width="15" height="140" rx="6">
<animate
attributeName="height"
begin="0s"
dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120"
calcMode="linear"
repeatCount="indefinite"
/>
<animate
attributeName="y"
begin="0s"
dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10"
calcMode="linear"
repeatCount="indefinite"
/>
</rect>
<rect x="90" y="10" width="15" height="120" rx="6">
<animate
attributeName="height"
begin="0.25s"
dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120"
calcMode="linear"
repeatCount="indefinite"
/>
<animate
attributeName="y"
begin="0.25s"
dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10"
calcMode="linear"
repeatCount="indefinite"
/>
</rect>
<rect x="120" y="10" width="15" height="120" rx="6">
<animate
attributeName="height"
begin="0.5s"
dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120"
calcMode="linear"
repeatCount="indefinite"
/>
<animate
attributeName="y"
begin="0.5s"
dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10"
calcMode="linear"
repeatCount="indefinite"
/>
</rect>
</svg>
</template>
File renamed without changes.
21 changes: 21 additions & 0 deletions frontend/src/services/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const BASE_URL = "http://localhost:8000/api";

function getErrorMessage(error: unknown) {
if (error instanceof Error) return error.message;
return String(error);
}

function reportError(message: string) {
console.error(message);
}

export async function getAllManderPaths(): Promise<string[]> {
try {
const r = await fetch(`${BASE_URL}/mandrs`);
const paths = await r.json();
return paths;
} catch (error) {
reportError(getErrorMessage(error));
return [];
}
}
3 changes: 3 additions & 0 deletions frontend/src/services/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function sleep(delay: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, delay));
}
99 changes: 32 additions & 67 deletions frontend/src/views/DashboardView.vue
Original file line number Diff line number Diff line change
@@ -1,72 +1,37 @@
<script setup lang="ts">
import FileTree, { type FileTreeNode } from "../components/FileTree.vue";
const records: FileTreeNode[] = [
{
label: "Punk Rock",
children: [
{
label: "The Clash",
children: [
{ label: "The Clash" },
{ label: "Give 'Em Enough Rope" },
{ label: "London Calling" },
{ label: "Sandinista!" },
{ label: "Combat Rock" },
{ label: "Cut the Crap" },
],
},
{
label: "Ramones",
children: [
{ label: "Ramones" },
{ label: "Leave Home" },
{ label: "Rocket to Russia" },
{ label: "Road to Ruin" },
{ label: "End of the Century" },
{ label: "Pleasant Dreams" },
{ label: "Subterranean Jungle" },
{ label: "Too Tough to Die" },
{ label: "Animal Boy" },
{ label: "Halfway to Sanity" },
{ label: "Brain Drain" },
{ label: "Mondo Bizarro" },
{ label: "Acid Eaters" },
{ label: "¡Adios Amigos!" },
],
},
],
},
{
label: "French touch",
children: [
{
label: "Laurent Garnier",
children: [
{ label: "Shot in the Dark" },
{ label: "Club Traxx EP" },
{ label: "30" },
{ label: "Early Works" },
{ label: "Unreasonable Behaviour" },
{ label: "The Cloud Making Machine" },
{ label: "Retrospective" },
{ label: "Public Outburst" },
{ label: "Tales of a Kleptomaniac" },
{ label: "Suivront Mille Ans De Calme" },
{ label: "Home Box" },
{ label: "Paris Est à Nous" },
{ label: "Le Roi Bâtard" },
{ label: "De Película" },
{ label: "Entre la Vie et la Mort" },
{ label: "33 tours et puis s'en vont" },
],
},
],
},
];
import { computed } from "vue";
import FileTree, { type FileTreeNode } from "@/components/FileTree.vue";
import { getAllManderPaths } from "@/services/api";
const manderPaths = await getAllManderPaths();
const pathsAsFileTreeNodes = computed(() => {
const tree: FileTreeNode[] = [];
for (let p of manderPaths) {
const slugs = p.split("/");
const rootSlug = slugs[0];
let currentNode = tree.find((n) => n.label == rootSlug);
if (!currentNode) {
currentNode = { label: rootSlug };
tree.push(currentNode);
}
let n = currentNode!;
for (let s of slugs.slice(1)) {
n.children = n.children || [];
let childNode = n.children.find((n) => n.label == s);
if (!childNode) {
childNode = { label: s };
n.children.push(childNode);
}
n = childNode;
}
}
return tree;
});
</script>

<template>
<main>
<FileTree :nodes="records" />
</main>
<div class="dashboard">
<FileTree :nodes="pathsAsFileTreeNodes"></FileTree>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it, expect } from "vitest";

import { mount } from "@vue/test-utils";
import FileTree, { type FileTreeNode } from "../FileTree.vue";
import FileTree, { type FileTreeNode } from "@/components/FileTree.vue";

function countLeaves(nodes: FileTreeNode[]): number {
function countInNode(node: FileTreeNode): number {
Expand All @@ -14,11 +14,11 @@ function countLeaves(nodes: FileTreeNode[]): number {
}

const allBranches = nodes.map((node) => countInNode(node));
return allBranches.reduce((accumulator, node) => accumulator + node);
return allBranches.reduce((accumulator, leavesCount) => accumulator + leavesCount);
}

describe("FileTree", () => {
it("renders properly", () => {
it("Renders properly.", () => {
const records: FileTreeNode[] = [
{
label: "Punk Rock",
Expand Down
22 changes: 22 additions & 0 deletions frontend/tests/services/api.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, it, expect } from "vitest";
import { getAllManderPaths } from "@/services/api";

import { createFetchResponse, mockedFecth } from "../test.utils";

describe("api", () => {
it("Can fetch the list of manders from the server.", async () => {
const paths = [
"probabl-ai/demo-usecase/training/0",
"probabl-ai/test-mandr/0",
"probabl-ai/test-mandr/1",
"probabl-ai/test-mandr/2",
"probabl-ai/test-mandr/3",
"probabl-ai/test-mandr/4",
];

mockedFecth.mockResolvedValue(createFetchResponse(paths));

const r = await getAllManderPaths();
expect(r).toStrictEqual(paths);
});
});
27 changes: 27 additions & 0 deletions frontend/tests/test.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { vi } from "vitest";
import { mount, flushPromises } from "@vue/test-utils";
import { type ComponentPublicInstance, defineComponent, Suspense, h } from "vue";

export const mockedFecth = vi.fn();
global.fetch = mockedFecth;

export function createFetchResponse(data: object) {
return { json: () => new Promise((resolve) => resolve(data)) };
}

export async function mountSuspens(
component: new () => ComponentPublicInstance,
options: any = {}
) {
const suspensedComponent = defineComponent({
render: () => {
return h(Suspense, null, {
default: h(component),
fallback: h("div", "loading..."),
});
},
});
const wrapper = mount(suspensedComponent, options);
await flushPromises();
return wrapper;
}
45 changes: 45 additions & 0 deletions frontend/tests/views/dashboard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, it, expect } from "vitest";
import { mountSuspens, mockedFecth, createFetchResponse } from "../test.utils";
import DashboardView from "@/views/DashboardView.vue";
import FileTree from "@/components/FileTree.vue";

describe("DashboardView", () => {
it("Parse the list of fetched list of mander to an array of FileTreeNode.", async () => {
const paths = [
"probabl-ai/demo-usecase/training/0",
"probabl-ai/test-mandr/0",
"probabl-ai/test-mandr/1",
"probabl-ai/test-mandr/2",
"probabl-ai/test-mandr/3",
"probabl-ai/test-mandr/4",
];

mockedFecth.mockResolvedValue(createFetchResponse(paths));

const wrapper = await mountSuspens(DashboardView);
const fileTree = await wrapper.getComponent(FileTree);
expect(fileTree.props()).toEqual({
nodes: [
{
label: "probabl-ai",
children: [
{
label: "demo-usecase",
children: [{ label: "training", children: [{ label: "0" }] }],
},
{
label: "test-mandr",
children: [
{ label: "0" },
{ label: "1" },
{ label: "2" },
{ label: "3" },
{ label: "4" },
],
},
],
},
],
});
});
});
Loading

0 comments on commit 7e76b45

Please sign in to comment.