Skip to content

Commit

Permalink
feat: Allow for custom ordering of templates (#153)
Browse files Browse the repository at this point in the history
* feat: Allow for custom ordering of templates

* fix: lint fixes
  • Loading branch information
ReinderVosDeWael authored Aug 30, 2024
1 parent f737986 commit b10a30d
Show file tree
Hide file tree
Showing 14 changed files with 438 additions and 228 deletions.
2 changes: 1 addition & 1 deletion src/lib/server/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import winston from "winston"

const { combine, timestamp, json } = winston.format
export const logger = winston.createLogger({
level: "info",
level: "debug",
format: combine(
timestamp({
format: "YYYY-MM-DD HH:mm:ss.SSS"
Expand Down
1 change: 1 addition & 0 deletions src/lib/server/sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type SqlTemplateSchema = {
id: number
text: string
parent_id: number | null
priority: number
}

export type SqlDsmCodeSchema = {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export function shortenText(str: string, maxLength = 200) {
if (str.length > maxLength) {
return str.substring(0, maxLength) + "..."
return str.substring(0, maxLength).trim() + "..."
}
return str
}
Expand Down
50 changes: 17 additions & 33 deletions src/routes/api/templates/[id]/+server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { logger } from "$lib/server/logging"
import { pool, type SqlTemplateSchema } from "$lib/server/sql"
import { pool } from "$lib/server/sql"

export async function POST({ request, params }) {
const body = await request.json()
Expand All @@ -26,45 +26,29 @@ export async function POST({ request, params }) {
})
}

export async function PATCH({ params, request }) {
const id = params.id
let { text, parent_id } = await request.json()
logger.info(`Patching template with id ${id}`)

const existingtemplate = await pool.connect().then(async client => {
const result = await client.query({
text: "SELECT * FROM templates WHERE id = $1",
values: [id]
})
client.release()
return result.rows[0] as SqlTemplateSchema
})
type PutRequest = {
text: string | null
parentId: number | null
priority: number | null
}

if (!existingtemplate) {
throw new Error(`template with id ${id} not found`)
}
if (existingtemplate) {
text = text ?? existingtemplate.text
parent_id = parent_id ?? existingtemplate.parent_id
}
export async function PUT({ params, request }) {
const id = params.id
const { text, parentId, priority } = (await request.json()) as PutRequest

const query = {
text: "UPDATE templates SET",
values: [] as string[]
if (text === null || parentId === null || priority === null) {
logger.error("Missing parameter in request.")
return new Response(null, { status: 400 })
}

if (text !== undefined) {
query.text += ` text = $${query.values.length + 1},`
query.values.push(text)
}
logger.info(`Patching template with id ${id}`)

if (parent_id !== undefined) {
query.text += ` parent_id = $${query.values.length + 1},`
query.values.push(String(parent_id))
const query = {
text: "UPDATE templates SET text = $1, parent_id = $2, priority = $3 WHERE id = $4",
values: [text, parentId, priority, id]
}

query.text = query.text.slice(0, -1) + ` WHERE id = $${query.values.length + 1}`
query.values.push(String(id))
logger.debug(query)

return await pool
.connect()
Expand Down
126 changes: 126 additions & 0 deletions src/routes/templates/DecisionTree.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { describe, it, expect } from "vitest"
import { DecisionTree } from "./DecisionTree"
import type { SqlTemplateSchema } from "$lib/server/sql"

describe("DecisionTree", () => {
const mockData: SqlTemplateSchema[] = [
{ id: 1, text: "Root", parent_id: null, priority: 0 },
{ id: 2, text: "Child 1", parent_id: 1, priority: 0 },
{ id: 3, text: "Child 2", parent_id: 1, priority: 1 },
{ id: 4, text: "Grandchild 1", parent_id: 2, priority: 0 },
{ id: 5, text: "Grandchild 2", parent_id: 2, priority: 1 }
]

it("should construct a tree correctly", () => {
const tree = new DecisionTree(mockData)

expect(tree.id).toBe(1)
expect(tree.text).toBe("Root")
expect(tree.children.length).toBe(2)
expect(tree.children[0].id).toBe(2)
expect(tree.children[1].id).toBe(3)
expect(tree.children[0].children.length).toBe(2)
})

it("should get parents correctly", () => {
const tree = new DecisionTree(mockData)
const grandchild = tree.getNodeById(4)

expect(grandchild).not.toBeNull()
if (grandchild) {
const parents = grandchild.getAncestors()
expect(parents.length).toBe(2)
expect(parents[0].id).toBe(1)
expect(parents[1].id).toBe(2)
}
})

it("should get children recursively", () => {
const tree = new DecisionTree(mockData)
const children = tree.getChildrenRecursive()

expect(children.length).toBe(4)
expect(children.map(c => c.id).sort()).toEqual([2, 3, 4, 5])
})

it("should filter children by ids", () => {
const tree = new DecisionTree(mockData)
const filtered = tree.filterChildrenByIds([3, 4])

expect(filtered.length).toBe(2)
expect(filtered.map(c => c.id).sort()).toEqual([3, 4])
})

it("should get path correctly", () => {
const tree = new DecisionTree(mockData)
const grandchild = tree.getNodeById(4)

expect(grandchild).not.toBeNull()

if (grandchild) {
const path = grandchild.getPath()
expect(path).toEqual(["Root", "Child 1"])
}
})

it("should get node by id", () => {
const tree = new DecisionTree(mockData)
const node = tree.getNodeById(3)
expect(node).not.toBeNull()
expect(node?.id).toBe(3)
expect(node?.text).toBe("Child 2")
})

it("should add child correctly", () => {
const tree = new DecisionTree(mockData)
const newChild = new DecisionTree([{ id: 6, text: "New Child", parent_id: 1, priority: 0 }], 6)

tree.addChild(newChild, 1)

expect(tree.children.length).toBe(3)
expect(tree.children[1].id).toBe(6)
expect(tree.children[1].priority).toBe(1)
expect(tree.children[2].priority).toBe(2)
})

it("should add child at end for index too large", () => {
const tree = new DecisionTree(mockData)
const newChild = new DecisionTree([{ id: 6, text: "New Child", parent_id: 1, priority: 0 }], 6)

tree.addChild(newChild, 99)

expect(tree.children.length).toBe(3)
expect(tree.children[2].id).toBe(6)
})

it("should delete child correctly", () => {
const tree = new DecisionTree(mockData)

tree.deleteChild(2)

expect(tree.children.length).toBe(1)
expect(tree.children[0].id).toBe(3)
expect(tree.children[0].priority).toBe(0)
})

it("should move child correctly", () => {
const tree = new DecisionTree(mockData)

tree.moveChild(3, 0)

expect(tree.children[0].id).toBe(3)
expect(tree.children[0].priority).toBe(0)
expect(tree.children[1].id).toBe(2)
})

it("should sort children recursively", () => {
const unsortedData: SqlTemplateSchema[] = [
{ id: 1, text: "Root", parent_id: null, priority: 0 },
{ id: 2, text: "Child 1", parent_id: 1, priority: 1 },
{ id: 3, text: "Child 2", parent_id: 1, priority: 0 }
]
const tree = new DecisionTree(unsortedData)
expect(tree.children[0].id).toBe(3)
expect(tree.children[1].id).toBe(2)
})
})
42 changes: 37 additions & 5 deletions src/routes/templates/DecisionTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ export class DecisionTree {
this.parent = parent
this.children = table
.filter(node => node.parent_id === this.id)
.sort((a, b) => a.priority - b.priority)
.map(child => new DecisionTree(table, child.id, this))
this.recursiveSortChildren()
}

getParents(): DecisionTree[] {
get priority(): number {
return this.parent?.children.findIndex(child => child.id === this.id) ?? 0
}

getAncestors(): DecisionTree[] {
const parents: DecisionTree[] = []
let current: DecisionTree | undefined = this.parent
while (current) {
Expand Down Expand Up @@ -71,14 +77,40 @@ export class DecisionTree {
return null
}

deleteNodeById(id: number): DecisionTree {
this.children = this.children.filter(child => child.id !== id)
this.children.forEach(child => child.deleteNodeById(id))
addChild(child: DecisionTree, index: number | undefined = undefined) {
if (index === undefined) {
index = this.children.length + 1
}
child.parent = this
this.children = [...this.children.slice(0, index), child, ...this.children.slice(index)]
return this
}

deleteChild(id: number) {
const childIndex = this.children.findIndex(child => child.id === id)
if (childIndex === -1) {
return
}
this.children.splice(childIndex, 1)
return this
}

moveChild(id: number, newIndex: number) {
if (newIndex >= this.children.length) return

const currentIndex = this.children.findIndex(child => child.id === id)
if (currentIndex === -1 || currentIndex === newIndex) return

const child = this.children[currentIndex]

this.deleteChild(id)
this.addChild(child, newIndex)

return this
}

recursiveSortChildren() {
this.children = this.children?.sort((a, b) => a.text.localeCompare(b.text))
this.children = this.children?.sort((a, b) => a.priority - b.priority)
this.children?.forEach(child => child.recursiveSortChildren())
}
}
Loading

0 comments on commit b10a30d

Please sign in to comment.