From 6a6b2cf912db4e0e96bd323254e6bef47c284600 Mon Sep 17 00:00:00 2001 From: Michael Haufe Date: Sat, 15 Jun 2024 18:37:19 -0500 Subject: [PATCH] Initial Use Case implementation --- modules/environment/domain/Invariant.ts | 4 +- .../system/application/UseCaseInteractor.ts | 61 +++++ modules/system/domain/TestCase.ts | 5 + modules/system/domain/UseCase.ts | 68 +++++- modules/system/mappers/UseCaseToJsonMapper.ts | 24 +- modules/system/ui/pages/Scenarios.vue | 211 +++++++++++++++++- 6 files changed, 362 insertions(+), 11 deletions(-) create mode 100644 modules/system/application/UseCaseInteractor.ts 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); +}