diff --git a/modules/environment/domain/Invariant.ts b/modules/environment/domain/Invariant.ts
index 13ef7c67..1044c2ed 100644
--- a/modules/environment/domain/Invariant.ts
+++ b/modules/environment/domain/Invariant.ts
@@ -1,6 +1,8 @@
import Requirement from "~/domain/Requirement";
/**
- * Environment property that must be maintained
+ * Environment property that must be maintained.
+ * It exists as both an assumption and an effect.
+ * (precondition and postcondition)
*/
export default class Invariant extends Requirement { }
diff --git a/modules/system/application/UseCaseInteractor.ts b/modules/system/application/UseCaseInteractor.ts
new file mode 100644
index 00000000..52c26b59
--- /dev/null
+++ b/modules/system/application/UseCaseInteractor.ts
@@ -0,0 +1,61 @@
+import Interactor from "~/application/Interactor"
+import UseCase from "../domain/UseCase"
+import type { Uuid } from "~/domain/Uuid"
+
+type In = Pick
+
+export default class UseCaseInteractor extends Interactor {
+ async create(
+ props: Omit
+ ): Promise {
+ return await this.repository.add(new UseCase({
+ id: crypto.randomUUID(),
+ extensions: props.extensions,
+ goalInContext: props.goalInContext,
+ level: props.level,
+ mainSuccessScenario: props.mainSuccessScenario,
+ name: props.name,
+ parentId: props.parentId,
+ preCondition: props.preCondition,
+ primaryActorId: props.primaryActorId,
+ property: '',
+ scope: props.scope,
+ solutionId: props.solutionId,
+ stakeHoldersAndInterests: props.stakeHoldersAndInterests,
+ successGuarantee: props.successGuarantee,
+ statement: '',
+ trigger: props.trigger
+ }))
+ }
+
+ async delete(id: Uuid): Promise {
+ await this.repository.delete(id)
+ }
+
+ async getAll(solutionId: Uuid): Promise {
+ return await this.repository.getAll(
+ useCase => useCase.solutionId === solutionId
+ )
+ }
+
+ async update(props: In): Promise {
+ await this.repository.update(new UseCase({
+ id: props.id,
+ extensions: props.extensions,
+ goalInContext: props.goalInContext,
+ level: props.level,
+ mainSuccessScenario: props.mainSuccessScenario,
+ name: props.name,
+ parentId: props.parentId,
+ preCondition: props.preCondition,
+ primaryActorId: props.primaryActorId,
+ property: '',
+ scope: props.scope,
+ solutionId: props.solutionId,
+ stakeHoldersAndInterests: props.stakeHoldersAndInterests,
+ successGuarantee: props.successGuarantee,
+ statement: '',
+ trigger: props.trigger
+ }))
+ }
+}
\ No newline at end of file
diff --git a/modules/system/domain/TestCase.ts b/modules/system/domain/TestCase.ts
index fad344d4..2909903b 100644
--- a/modules/system/domain/TestCase.ts
+++ b/modules/system/domain/TestCase.ts
@@ -1,3 +1,8 @@
import Example from "./Example";
+/**
+ * A TestCase is a specification of the inputs, execution conditions,
+ * testing procedure, and expected results that define a single test to
+ * be executed to achieve a particular goal.,
+ */
export default class TestCase extends Example { }
\ No newline at end of file
diff --git a/modules/system/domain/UseCase.ts b/modules/system/domain/UseCase.ts
index d39a3b94..8b9bacdf 100644
--- a/modules/system/domain/UseCase.ts
+++ b/modules/system/domain/UseCase.ts
@@ -1,7 +1,73 @@
import Scenario from "./Scenario";
+import type { Uuid } from "~/domain/Uuid";
+import type { Properties } from "~/domain/Properties";
/**
* A Use Case specifies the scenario of a complete
* interaction of a user through a system.
*/
-export default class UseCase extends Scenario { }
\ No newline at end of file
+export default class UseCase extends Scenario {
+ /**
+ * TODO:
+ */
+ scope: string
+
+ /**
+ * TODO:
+ */
+ level: string
+
+ /**
+ * TODO: is this just the Goal.description?
+ */
+ goalInContext: string
+
+ /**
+ * The preCondition is an Assumption that must be true before the use case can start.
+ */
+ preCondition: Uuid
+
+ // the action upon the system that starts the use case
+ // A Responsibility? Functional Requirement?
+ trigger: Uuid
+
+ /**
+ * The main success scenario is the most common path through the system.
+ * It takes the form of a sequence of steps that describe the interaction:
+ * 1. The use case starts when .
+ * 2. The system .
+ * 3. The does something else.
+ * ...
+ */
+ //mainSuccessScenario: [FunctionalRequirement | Constraint | Role | Responsibility][]
+ mainSuccessScenario: string
+
+ /**
+ * An Effect that is guaranteed to be true after the use case is completed.
+ */
+ successGuarantee: Uuid
+
+ /**
+ *
+ */
+ // extensions: [FunctionalRequirement | Constraint | Role | Responsibility][]
+ extensions: string
+
+ /**
+ *
+ */
+ stakeHoldersAndInterests: Uuid[] // Actor[]
+
+ constructor(props: Properties) {
+ super(props)
+ this.scope = props.scope
+ this.level = props.level
+ this.goalInContext = props.goalInContext
+ this.preCondition = props.preCondition
+ this.trigger = props.trigger
+ this.mainSuccessScenario = props.mainSuccessScenario
+ this.successGuarantee = props.successGuarantee
+ this.extensions = props.extensions
+ this.stakeHoldersAndInterests = props.stakeHoldersAndInterests
+ }
+}
\ No newline at end of file
diff --git a/modules/system/mappers/UseCaseToJsonMapper.ts b/modules/system/mappers/UseCaseToJsonMapper.ts
index e72f4872..f34ab4f2 100644
--- a/modules/system/mappers/UseCaseToJsonMapper.ts
+++ b/modules/system/mappers/UseCaseToJsonMapper.ts
@@ -1,7 +1,18 @@
import ScenarioToJsonMapper, { type ScenarioJson } from "./ScenarioToJsonMapper";
import UseCase from "../domain/UseCase";
+import type { Uuid } from "~/domain/Uuid";
-export interface UseCaseJson extends ScenarioJson { }
+export interface UseCaseJson extends ScenarioJson {
+ scope: string
+ level: string
+ goalInContext: string
+ preCondition: Uuid
+ trigger: Uuid
+ mainSuccessScenario: string
+ successGuarantee: Uuid
+ extensions: string
+ stakeHoldersAndInterests: Uuid[]
+}
export default class UseCaseToJsonMapper extends ScenarioToJsonMapper {
override mapFrom(target: UseCaseJson): UseCase {
@@ -10,7 +21,16 @@ export default class UseCaseToJsonMapper extends ScenarioToJsonMapper {
override mapTo(source: UseCase): UseCaseJson {
return {
- ...super.mapTo(source)
+ ...super.mapTo(source),
+ scope: source.scope,
+ level: source.level,
+ goalInContext: source.goalInContext,
+ preCondition: source.preCondition,
+ trigger: source.trigger,
+ mainSuccessScenario: source.mainSuccessScenario,
+ successGuarantee: source.successGuarantee,
+ extensions: source.extensions,
+ stakeHoldersAndInterests: source.stakeHoldersAndInterests
};
}
}
\ No newline at end of file
diff --git a/modules/system/ui/pages/Scenarios.vue b/modules/system/ui/pages/Scenarios.vue
index 3d57f0f8..e9f4c1c3 100644
--- a/modules/system/ui/pages/Scenarios.vue
+++ b/modules/system/ui/pages/Scenarios.vue
@@ -6,6 +6,7 @@ import UserStoryInteractor from '../../application/UserStoryInteractor';
import GetSolutionBySlugUseCase from '~/modules/solution/application/GetSolutionBySlugUseCase';
import GetSystemBySolutionIdUseCase from '../../application/GetSystemBySolutionIdUseCase';
import type UserStory from '../../domain/UserStory';
+import type UseCase from '../../domain/UseCase';
import { emptyUuid, type Uuid } from '~/domain/Uuid';
import { FilterMatchMode } from 'primevue/api';
import EpicRepository from '~/modules/goals/data/EpicRepository';
@@ -19,6 +20,9 @@ import StakeholderRepository from '~/modules/goals/data/StakeholderRepository';
import GetStakeHoldersUseCase from '~/modules/goals/application/GetStakeHoldersUseCase';
import Stakeholder from '~/modules/goals/domain/Stakeholder';
import EpicInteractor from '~/modules/goals/application/EpicInteractor';
+import type { Properties } from '~/domain/Properties';
+import UseCaseInteractor from '../../application/UseCaseInteractor';
+import UseCaseRepository from '../../data/UseCaseRepository';
useHead({
title: 'Scenarios'
@@ -32,6 +36,7 @@ const router = useRouter(),
userStoryRepository = new UserStoryRepository(),
goalsRepository = new GoalsRepository(),
epicRepository = new EpicRepository(),
+ useCaseRepository = new UseCaseRepository(),
stakeholderRepository = new StakeholderRepository(),
functionalRequirementsRepository = new FunctionalRequirementRepository(),
userStoryInteractor = new UserStoryInteractor(userStoryRepository),
@@ -41,6 +46,7 @@ const router = useRouter(),
getGoalsBySolutionIdUseCase = new GetGoalsBySolutionIdUseCase(goalsRepository),
functionalRequirementInteractor = new FunctionalRequirementInteractor(functionalRequirementsRepository),
epicInteractor = new EpicInteractor(epicRepository),
+ useCaseInteractor = new UseCaseInteractor(useCaseRepository),
getStakeholdersUseCase = new GetStakeHoldersUseCase(stakeholderRepository),
system = solution?.id && await getSystemBySolutionIdUseCase.execute(solution.id),
goals = solution?.id && await getGoalsBySolutionIdUseCase.execute(solution.id)
@@ -54,7 +60,10 @@ if (!solution) {
type UserStoryViewModel = Pick
+type UseCaseViewModel = Pick
+
const userStories = ref([]),
+ useCases = ref([]),
emptyUserStory: UserStoryViewModel = {
id: emptyUuid,
name: '',
@@ -62,6 +71,20 @@ const userStories = ref([]),
behaviorId: emptyUuid,
epicId: emptyUuid
},
+ emptyUseCase: UseCaseViewModel = {
+ id: emptyUuid,
+ name: '',
+ primaryActorId: emptyUuid,
+ extensions: '',
+ goalInContext: '',
+ level: '',
+ mainSuccessScenario: '',
+ preCondition: emptyUuid,
+ scope: '',
+ stakeHoldersAndInterests: [],
+ successGuarantee: emptyUuid,
+ trigger: emptyUuid
+ },
roles = ref([]),
behaviors = ref([]),
epics = ref([]);
@@ -71,16 +94,31 @@ onMounted(async () => {
roles.value = await getStakeholdersUseCase.execute(goals!.id);
behaviors.value = await functionalRequirementInteractor.getAll(solution!.id);
epics.value = await epicInteractor.getAll(solution!.id);
+ useCases.value = await useCaseInteractor.getAll(solution!.id);
})
-const filters = ref({
+const userStoryfilters = ref({
'name': { value: null, matchMode: FilterMatchMode.CONTAINS },
'primaryActorId': { value: null, matchMode: FilterMatchMode.EQUALS },
'behaviorId': { value: null, matchMode: FilterMatchMode.EQUALS },
'epicId': { value: null, matchMode: FilterMatchMode.EQUALS }
})
-const onCreate = async (userStory: UserStoryViewModel) => {
+const useCasefilters = ref({
+ 'name': { value: null, matchMode: FilterMatchMode.CONTAINS },
+ 'primaryActorId': { value: null, matchMode: FilterMatchMode.EQUALS },
+ 'extensions': { value: null, matchMode: FilterMatchMode.CONTAINS },
+ 'goalInContext': { value: null, matchMode: FilterMatchMode.CONTAINS },
+ 'level': { value: null, matchMode: FilterMatchMode.CONTAINS },
+ 'mainSuccessScenario': { value: null, matchMode: FilterMatchMode.CONTAINS },
+ 'preCondition': { value: null, matchMode: FilterMatchMode.EQUALS },
+ 'scope': { value: null, matchMode: FilterMatchMode.CONTAINS },
+ 'stakeHoldersAndInterests': { value: null, matchMode: FilterMatchMode.CONTAINS },
+ 'successGuarantee': { value: null, matchMode: FilterMatchMode.EQUALS },
+ 'trigger': { value: null, matchMode: FilterMatchMode.EQUALS }
+})
+
+const onUserStoryCreate = async (userStory: UserStoryViewModel) => {
const newId = await userStoryInteractor.create({
...userStory,
solutionId: solution!.id,
@@ -90,7 +128,17 @@ const onCreate = async (userStory: UserStoryViewModel) => {
userStories.value = await userStoryInteractor.getAll(system!.id);
}
-const onUpdate = async (userStory: UserStoryViewModel) => {
+const onUseCaseCreate = async (useCase: UseCaseViewModel) => {
+ const newId = await useCaseInteractor.create({
+ ...useCase,
+ solutionId: solution!.id,
+ parentId: system!.id
+ });
+
+ useCases.value = await useCaseInteractor.getAll(system!.id);
+}
+
+const onUserStoryUpdate = async (userStory: UserStoryViewModel) => {
await userStoryInteractor.update({
...userStory,
parentId: system!.id,
@@ -100,11 +148,27 @@ const onUpdate = async (userStory: UserStoryViewModel) => {
userStories.value = await userStoryInteractor.getAll(system!.id);
}
-const onDelete = async (id: Uuid) => {
+const onUseCaseUpdate = async (useCase: UseCaseViewModel) => {
+ await useCaseInteractor.update({
+ ...useCase,
+ parentId: system!.id,
+ solutionId: solution!.id
+ });
+
+ useCases.value = await useCaseInteractor.getAll(system!.id);
+}
+
+const onUserStoryDelete = async (id: Uuid) => {
await userStoryInteractor.delete(id);
userStories.value = await userStoryInteractor.getAll(system!.id);
}
+
+const onUseCaseDelete = async (id: Uuid) => {
+ await useCaseInteractor.delete(id);
+
+ useCases.value = await useCaseInteractor.getAll(system!.id);
+}
@@ -119,8 +183,8 @@ const onDelete = async (id: Uuid) => {
leveraging a particular behavior of the system.
-
+
{
- Use Cases
+ A Use Case describes a complete interaction between an actor and the
+ system to achieve a goal.
+
+
+
+
+
+
+ {{ data[field] }}
+
+
+
+
+
+
+
+
+
+
+ {{ data[field] }}
+
+
+
+
+
+
+
+
+
+
+ {{ data[field] }}
+
+
+
+
+
+
+
+
+
+
+ {{ roles.find(r => r.id === data[field])?.name }}
+
+
+
+
+
+
+
+
+
+
+ {{ data[field] }}
+
+
+
+
+
+
+
+
+
+
+ {{ behaviors.find(b => b.id === data[field])?.name }}
+
+
+
+
+
+
+
+
+
+
+ {{ behaviors.find(b => b.id === data[field])?.name }}
+
+
+
+
+
+
+
+ {{ data[field] }}
+
+
+
+
+
+
+
+
+
+
+ {{ behaviors.find(b => b.id === data[field])?.name }}
+
+
+
+
+
+
+
+ {{ data[field] }}
+
+
+
+
+
+
+
+ {{
+ data[field].map((id: Uuid) => roles.find((r: any) => r.id === id)?.name).join(', ')
+ }}
+
+
+
+
+
+
\ No newline at end of file