Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split rationale page #415

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions migrations/Migration20241029171100.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Migration } from '@mikro-orm/migrations';

export class Migration20241029171100 extends Migration {
override async up(): Promise<void> {
// There is currently a record { req_type: 'justification', name: 'Situation', ...} in the requirement table
// Update the req_type to 'obstacle' and the name to 'G.2'
this.addSql(`
UPDATE requirement
SET req_type = 'obstacle', name = 'G.2'
WHERE req_type = 'justification' AND name = 'Situation';
`);

// There are currently records in the requirement table with req_type = 'justification' and name in name in ('Vision', 'Mission', 'Objective')
// Update the req_type of the 'Vision' record to 'goal' and the name to 'G.1'
// Update the description field of the 'Vision' record to '# Vision\n\n${description1}\n\n# Mission\n\n${description2}\n\n# Objective\n\n${description3}'
// Where description1, description2, and description3 are the descriptions of the 'Vision', 'Mission', and 'Objective' records respectively
this.addSql(`
WITH description1 AS (
SELECT description
FROM requirement
WHERE req_type = 'justification' AND name = 'Vision'
),
description2 AS (
SELECT description
FROM requirement
WHERE req_type = 'justification' AND name = 'Mission'
),
description3 AS (
SELECT description
FROM requirement
WHERE req_type = 'justification' AND name = 'Objective'
)
UPDATE requirement
SET req_type = 'goal', name = 'G.1', description = E'# Vision\n\n' || (SELECT description FROM description1) || E'\n\n# Mission\n\n' || (SELECT description FROM description2) || E'\n\n# Objective\n\n' || (SELECT description FROM description3)
WHERE req_type = 'justification' AND name = 'Vision';
`);

// delete the Mission and Objective records from the requirement table
this.addSql(`
DELETE FROM requirement
WHERE req_type = 'justification' AND name IN ('Mission', 'Objective');
`);
}

override async down(): Promise<void> { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script lang="ts" setup>
type GoalViewModel = {
id: string;
name: string;
description: string;
};

useHead({ title: 'Context and Objective' })
definePageMeta({ name: 'Context and Objective' })

const { $eventBus } = useNuxtApp(),
{ solutionslug, organizationslug } = useRoute('Context and Objective').params,
{ data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, {
query: {
slug: solutionslug,
organizationSlug: organizationslug
}
}),
solutionId = solutions.value?.[0].id;

if (getSolutionError.value)
$eventBus.$emit('page-error', getSolutionError.value);

const { data: goals, error: getGoalsError } = await useFetch<GoalViewModel[]>(`/api/goal`, { query: { name: 'G.1', solutionId } });

if (getGoalsError.value)
$eventBus.$emit('page-error', getGoalsError.value);

const contextObjectiveDescription = ref(goals.value?.[0].description!),
contextObjective = goals.value![0]

watch(contextObjectiveDescription, debounce(() => {
contextObjective.description = contextObjectiveDescription.value;
$fetch(`/api/goal/${contextObjective.id}`, {
method: 'PUT',
body: {
solutionId,
name: contextObjective.name,
description: contextObjective.description
}
}).catch((e) => $eventBus.$emit('page-error', e));
}, 500));
</script>

<template>
<form autocomplete="off">
<h2>Context and Objective</h2>
<div class="field">
<p>
High-level view of the project: organizational context and reason for building a system
</p>
<Textarea name="contextObjective" id="contextObjective" class="w-full h-10rem"
v-model.trim.lazy="contextObjectiveDescription" />
</div>
</form>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ definePageMeta({ name: 'Goals' })
const { solutionslug, organizationslug } = useRoute('Goals').params

const links = [
{ name: 'Rationale' as const, icon: 'pi-book', label: 'Rationale' },
{ name: 'Context and Objective' as const, icon: 'pi-bullseye', label: 'Context and Objective' },
{ name: 'Situation' as const, icon: 'pi-search', label: 'Situation' },
{ name: 'Outcomes' as const, icon: 'pi-check-circle', label: 'Outcomes' },
{ name: 'Stakeholders' as const, icon: 'pi-users', label: 'Stakeholders' },
{ name: 'Goal Scenarios' as const, icon: 'pi-briefcase', label: 'Scenarios' },
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script lang="ts" setup>
type ObstacleViewModel = {
id: string;
name: string;
description: string;
};

useHead({ title: 'Situation' })
definePageMeta({ name: 'Situation' })

const { $eventBus } = useNuxtApp(),
{ solutionslug, organizationslug } = useRoute('Situation').params,
{ data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, {
query: {
slug: solutionslug,
organizationSlug: organizationslug
}
}),
solutionId = solutions.value?.[0].id;

if (getSolutionError.value)
$eventBus.$emit('page-error', getSolutionError.value);

const { data: situations, error: getSituationError } = await useFetch<ObstacleViewModel[]>(`/api/obstacle`, { query: { name: 'G.2', solutionId } });

if (getSituationError.value)
$eventBus.$emit('page-error', getSituationError.value);

const situationDescription = ref(situations.value?.[0].description!),
situation = situations.value![0]

watch(situationDescription, debounce(() => {
situation.description = situationDescription.value;
$fetch(`/api/obstacle/${situation.id}`, {
method: 'PUT',
body: {
solutionId,
name: situation.name,
description: situation.description
}
}).catch((e) => $eventBus.$emit('page-error', e));
}, 500));
</script>

<template>
<form autocomplete="off">
<h2>Situation</h2>
<div class="field">
<p>
The situation is the current state of affairs that need to be
addressed by the system created by a project.
</p>
<Textarea name="situation" id="situation" class="w-full h-10rem" v-model.trim.lazy="situationDescription" />
</div>
</form>
</template>
24 changes: 24 additions & 0 deletions server/api/goal/[id].delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { z } from "zod"
import { fork } from "~/server/data/orm.js"
import { Goal } from "~/domain/requirements/index.js"

const paramSchema = z.object({
id: z.string().uuid()
})

const bodySchema = z.object({
solutionId: z.string().uuid()
})

/**
* Delete a goal by id.
*/
export default defineEventHandler(async (event) => {
const { id } = await validateEventParams(event, paramSchema),
{ solutionId } = await validateEventBody(event, bodySchema),
{ solution } = await assertSolutionContributor(event, solutionId),
em = fork(),
goal = await assertReqBelongsToSolution(em, Goal, id, solution)

await em.removeAndFlush(goal)
})
24 changes: 24 additions & 0 deletions server/api/goal/[id].get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { z } from "zod"
import { fork } from "~/server/data/orm.js"
import { Goal } from "~/domain/requirements/index.js"

const paramSchema = z.object({
id: z.string().uuid()
})

const querySchema = z.object({
solutionId: z.string().uuid()
})

/**
* Returns a Goal by id
*/
export default defineEventHandler(async (event) => {
const { id } = await validateEventParams(event, paramSchema),
{ solutionId } = await validateEventQuery(event, querySchema),
{ solution } = await assertSolutionReader(event, solutionId),
em = fork(),
goal = await assertReqBelongsToSolution(em, Goal, id, solution)

return goal
})
36 changes: 36 additions & 0 deletions server/api/goal/[id].put.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { fork } from "~/server/data/orm.js"
import { z } from "zod"
import { Goal } from "~/domain/requirements/index.js"

const paramSchema = z.object({
id: z.string().uuid()
})

const bodySchema = z.object({
solutionId: z.string().uuid(),
name: z.string().optional(),
description: z.string().optional(),
isSilence: z.boolean().optional()
})

/**
* Updates a Goal by id.
*/
export default defineEventHandler(async (event) => {
const { id } = await validateEventParams(event, paramSchema),
{ solutionId } = await validateEventBody(event, bodySchema),
{ sessionUser, solution } = await assertSolutionContributor(event, solutionId),
{ name, description, isSilence } = await validateEventBody(event, bodySchema),
em = fork(),
goal = await assertReqBelongsToSolution(em, Goal, id, solution)

goal.assign({
name: name ?? goal.name,
description: description ?? goal.description,
isSilence: isSilence ?? goal.isSilence,
modifiedBy: sessionUser,
lastModified: new Date()
})

await em.persistAndFlush(goal)
})
22 changes: 22 additions & 0 deletions server/api/goal/index.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { z } from "zod"
import { fork } from "~/server/data/orm.js"
import { Goal, ReqType } from "~/domain/requirements/index.js"

const querySchema = z.object({
solutionId: z.string().uuid(),
name: z.string().optional(),
description: z.string().optional(),
isSilence: z.boolean().optional().default(false)
})

/**
* Returns all goals that match the query parameters
*/
export default defineEventHandler(async (event) => {
const query = await validateEventQuery(event, querySchema),
em = fork()

await assertSolutionReader(event, query.solutionId)

return await findAllSolutionRequirements<Goal>(ReqType.GOAL, em, query)
})
Loading