Skip to content

Commit

Permalink
Better store of project data, and new /api/projects endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
scosman committed Oct 9, 2024
1 parent 2ee1a4f commit e4de408
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 29 deletions.
67 changes: 67 additions & 0 deletions app/web_ui/src/lib/stores.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { get } from "svelte/store"
import { projects, current_project } from "./stores"
import { describe, it, expect, beforeEach } from "vitest"

const testProject = {
name: "Test Project",
path: "/test/path",
description: "Test Description",
created_at: new Date(),
created_by: "Test User",
}

describe("stores", () => {
beforeEach(() => {
// Reset the projects store before each test
projects.set(null)
})

describe("projects store", () => {
it("should initialize with null", () => {
expect(get(projects)).toBeNull()
})

it("should update when set", () => {
const testProjects = {
projects: [testProject],
current_project_path: "/test/path",
error: null,
}
projects.set(testProjects)
expect(get(projects)).toEqual(testProjects)
})
})

describe("current_project function", () => {
it("should return null when projects store is null", () => {
expect(current_project()).toBeNull()
})

it("should return null when current_project_path is null", () => {
projects.set({
projects: [testProject],
current_project_path: null,
error: null,
})
expect(current_project()).toBeNull()
})

it("should return null when no project matches current_project_path", () => {
projects.set({
projects: [testProject],
current_project_path: "/non-existent/path",
error: null,
})
expect(current_project()).toBeNull()
})

it("should return the correct project when it exists", () => {
projects.set({
projects: [testProject],
current_project_path: "/test/path",
error: null,
})
expect(current_project()).toEqual(testProject)
})
})
})
60 changes: 58 additions & 2 deletions app/web_ui/src/lib/stores.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,59 @@
import { writable } from "svelte/store"
import { writable, get } from "svelte/store"
import { post_error_handler, createKilnError } from "./utils/error_handlers"

export const current_project = writable<string | null>(null)
type ProjectInfo = {
name: string
description: string
path: string
created_at: Date
created_by: string
}

type AllProjects = {
projects: ProjectInfo[]
current_project_path: string | null
error: string | null
}

export const projects = writable<AllProjects | null>(null)

export function current_project(): ProjectInfo | null {
const all_projects = get(projects)

if (!all_projects) {
return null
}
const current_project_path = all_projects.current_project_path
if (!current_project_path) {
return null
}
const project = all_projects.projects.find(
(project) => project.path === current_project_path,
)
if (!project) {
return null
}
return project
}

export async function load_projects() {
try {
const response = await fetch("http://localhost:8757/api/projects")
const data = await response.json()
post_error_handler(response, data)

const all_projects: AllProjects = {
projects: data.projects,
current_project_path: data.current_project_path,
error: null,
}
projects.set(all_projects)
} catch (error: unknown) {
const all_projects: AllProjects = {
projects: [],
current_project_path: null,
error: "Issue loading projects. " + createKilnError(error).getMessage(),
}
projects.set(all_projects)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
})
// Trigger reactivity
schema_model = schema_model
console.log(schema_model)
// Scroll new item into view. Async to allow rendering first
setTimeout(() => {
const property = document.getElementById(
Expand Down
2 changes: 0 additions & 2 deletions app/web_ui/src/routes/(app)/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script>
import "../../app.css"
import { current_project } from "$lib/stores"
</script>

<div class="drawer lg:drawer-open">
Expand Down Expand Up @@ -55,7 +54,6 @@
</li>
<li><a href="/?1">Sidebar Item 1</a></li>
<li><a href="/?2">Sidebar Item 2</a></li>
<li>{$current_project}</li>
</ul>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from "$app/navigation"
import { current_project } from "$lib/stores"
import { load_projects } from "$lib/stores"
import FormContainer from "$lib/utils/form_container.svelte"
import FormElement from "$lib/utils/form_element.svelte"
import {
Expand Down Expand Up @@ -36,14 +36,9 @@
})
const data = await response.json()
post_error_handler(response, data)
if (data["path"]) {
current_project.set(data["path"])
} else {
throw new KilnError(
"Project created, but failed to return location.",
null,
)
}
// now reload the projects, which should fetch the new project as current_project
await load_projects()
error = null
if (redirect_on_created) {
goto(redirect_on_created)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
async function create_task() {
try {
if (!$current_project) {
if (!current_project()) {
error = new KilnError(
"You must create a project before creating a task",
null,
Expand All @@ -62,7 +62,11 @@
schema_from_model(task_output_schema),
)
}
const encodedProjectPath = encodeURIComponent($current_project)
const project_path = current_project()?.path
if (!project_path) {
throw new KilnError("Current project not found", null)
}
const encodedProjectPath = encodeURIComponent(project_path)
const response = await fetch(
`http://localhost:8757/api/task?project_path=${encodedProjectPath}`,
{
Expand Down
50 changes: 37 additions & 13 deletions app/web_ui/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,56 @@
import { slide } from "svelte/transition"
import { onMount } from "svelte"
import { goto } from "$app/navigation"
import { current_project } from "$lib/stores"
import { current_project, load_projects, projects } from "$lib/stores"
import { get } from "svelte/store"
import { KilnError } from "$lib/utils/error_handlers"
import { createKilnError } from "$lib/utils/error_handlers"
let loading = true
let load_error: string | null = null
const check_needs_setup = async () => {
try {
let res = await fetch("http://localhost:8757/api/settings")
let data = await res.json()
let projects = data["projects"]
let current_project_path = data["current_project"]
if (!projects || projects.length === 0) {
await load_projects()
const all_projects = get(projects)
if (all_projects?.error) {
throw new KilnError(all_projects.error, null)
}
if (!current_project()) {
goto("/setup")
} else {
// Set the current_project to the current project, or first project
current_project.set(current_project_path || projects[0])
}
} catch (e) {
console.error("check_needs_setup error", e)
} catch (e: unknown) {
load_error = createKilnError(e).getMessage()
} finally {
loading = false
}
}
onMount(() => {
// Check if we need setup (async okay)
check_needs_setup()
})
</script>

{#if loading || load_error}
<div
class="fixed w-full top-0 right-0 left-0 bottom-0 bg-base-200 z-[1000] flex place-items-center place-content-center"
>
{#if load_error}
<span class="text-center flex flex-col gap-4">
<h1 class="text-2xl font-bold">Error loading projects</h1>
<p class="text-error">{load_error}</p>
<button
class="btn btn-primary btn-sm"
on:click={() => window.location.reload()}
>
Retry
</button>
</span>
{:else}
<span class="loading loading-spinner loading-lg"></span>
{/if}
</div>
{/if}

{#if $navigating}
<!--
Loading animation for next page since svelte doesn't show any indicator.
Expand Down
31 changes: 31 additions & 0 deletions libs/studio/kiln_studio/project_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,34 @@ async def create_project(project: Project):
returnProject = project.model_dump()
returnProject["path"] = project.path
return returnProject

@app.get("/api/projects")
async def get_projects():
project_paths = Config.shared().projects
projects = []
for project_path in project_paths:
try:
project = Project.load_from_file(project_path)
json_project = project.model_dump()
path = str(project.path)
if path is None:
raise ValueError("Project path is None")
json_project["path"] = path
projects.append(json_project)
except Exception as e:
print(f"Error loading project, skipping: {project_path}: {e}")

current_project_path = None
if Config.shared().current_project is not None:
current_project_path = str(Config.shared().current_project)

# Check if the current project path is in the list of projects
if not current_project_path or current_project_path not in [
project["path"] for project in projects
]:
current_project_path = projects[0]["path"] if len(projects) > 0 else None

return {
"projects": projects,
"current_project_path": current_project_path,
}
Loading

0 comments on commit e4de408

Please sign in to comment.