Skip to content

Commit

Permalink
Merge pull request #155 from trustbit/chore/required-fields
Browse files Browse the repository at this point in the history
Chore/required fields
  • Loading branch information
K-Dud authored Jan 16, 2024
2 parents 671b66d + 931b947 commit 87d30a2
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 59 deletions.
35 changes: 23 additions & 12 deletions backend/Contexture.Api/Entities/BoundedContext.fs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ type Command =
| UpdateDomainRoles of BoundedContextId * UpdateDomainRoles
| UpdateMessages of BoundedContextId * UpdateMessages

and CreateBoundedContext = { Name: string }
and CreateBoundedContext = { Name: string; ShortName: string option; Description: string option }
and RenameBoundedContext = { Name: string }

and AssignShortName = { ShortName: string }
Expand Down Expand Up @@ -214,6 +214,20 @@ type State =
let nameValidation name =
if String.IsNullOrWhiteSpace name then Error EmptyName else Ok name

let assignShortNameToBoundedContext shortName boundedContextId =
ShortNameAssigned
{ BoundedContextId = boundedContextId
ShortName =
shortName
|> Option.filter (String.IsNullOrWhiteSpace >> not) }
|> Ok

let changeDescription descriptionText contextId =
DescriptionChanged
{ Description = descriptionText
BoundedContextId = contextId }
|> Ok

let newBoundedContext id domainId name =
name
|> nameValidation
Expand All @@ -223,6 +237,12 @@ let newBoundedContext id domainId name =
DomainId = domainId
Name = name })

let createBoundedContext id domainId name shortName description =
FsToolkit.ErrorHandling.Result.map3 (fun a b c -> [a;b;c])
(newBoundedContext id domainId name)
(assignShortNameToBoundedContext shortName id)
(changeDescription description id)

let renameBoundedContext potentialName boundedContextId =
potentialName
|> nameValidation
Expand All @@ -231,22 +251,13 @@ let renameBoundedContext potentialName boundedContextId =
{ Name = name
BoundedContextId = boundedContextId })

let assignShortNameToBoundedContext shortName boundedContextId =
ShortNameAssigned
{ BoundedContextId = boundedContextId
ShortName =
shortName
|> Option.ofObj
|> Option.filter (String.IsNullOrWhiteSpace >> not) }
|> Ok

let private asList item = item |> Result.map List.singleton

let decide (command: Command) state =
match command with
| CreateBoundedContext (id, domainId, createBc) -> newBoundedContext id domainId createBc.Name |> asList
| CreateBoundedContext (id, domainId, createBc) -> createBoundedContext id domainId createBc.Name createBc.ShortName createBc.Description
| RenameBoundedContext (contextId, rename) -> renameBoundedContext rename.Name contextId |> asList
| AssignShortName (contextId, shortName) -> assignShortNameToBoundedContext shortName.ShortName contextId |> asList
| AssignShortName (contextId, shortName) -> assignShortNameToBoundedContext (shortName.ShortName |> Option.ofObj) contextId |> asList
| RemoveBoundedContext contextId ->
match state with
| Existing domain ->
Expand Down
81 changes: 45 additions & 36 deletions backend/Contexture.Api/Entities/Domain.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ namespace Contexture.Api.Aggregates

module Domain =
open System
open FsToolkit.ErrorHandling

module ValueObjects =
type DomainId = Guid

Expand All @@ -15,7 +17,7 @@ module Domain =
| AssignShortName of DomainId * AssignShortName
| RemoveDomain of DomainId

and CreateDomain = { Name: string }
and CreateDomain = { Name: string; Vision: string option; ShortName: string option }

and RenameDomain = { Name: string }

Expand Down Expand Up @@ -43,7 +45,7 @@ module Domain =
Name: string
Vision: string option }

and DomainCreated = { DomainId: DomainId; Name: String }
and DomainCreated = { DomainId: DomainId; Name: String; }

and SubDomainCreated =
{ DomainId: DomainId
Expand Down Expand Up @@ -147,17 +149,42 @@ module Domain =
Existing { domain with ShortName = c.ShortName }
| _ -> state

let newDomain id name parentDomain =
name
|> nameValidation
|> Result.map (fun name ->
match parentDomain with
| Some parent ->
SubDomainCreated
{ DomainId = id
ParentDomainId = parent
Name = name }
| None -> DomainCreated { DomainId = id; Name = name })
let assignShortNameToDomain shortName domainId =
ShortNameAssigned
{ DomainId = domainId
ShortName =
shortName
|> Option.filter (String.IsNullOrWhiteSpace >> not) }
|> Ok

let refineVisionOfDomain vision domainId =
VisionRefined
{ DomainId = domainId
Vision =
vision
|> Option.filter (String.IsNullOrWhiteSpace >> not) }
|> Ok

let newDomain id name shortName vision parentDomain = result {
let! created =
name
|> nameValidation
|> Result.map (fun name ->
match parentDomain with
| Some parent ->
SubDomainCreated
{
DomainId = id
ParentDomainId = parent
Name = name
}
| None -> DomainCreated { DomainId = id; Name = name })

let! shortNameEvent = assignShortNameToDomain shortName id
let! visionEvent = refineVisionOfDomain vision id

return [created; shortNameEvent; visionEvent]
}

let moveDomain (state:State) parent domainId =
match state with
Expand All @@ -179,15 +206,6 @@ module Domain =
| _ ->
Ok []

let refineVisionOfDomain vision domainId =
VisionRefined
{ DomainId = domainId
Vision =
vision
|> Option.ofObj
|> Option.filter (String.IsNullOrWhiteSpace >> not) }
|> Ok

let renameDomain potentialName domainId state =
match state with
| Existing name ->
Expand All @@ -201,16 +219,7 @@ module Domain =
})
| _ ->
Error DomainAlreadyDeleted

let assignShortNameToDomain shortName domainId =
ShortNameAssigned
{ DomainId = domainId
ShortName =
shortName
|> Option.ofObj
|> Option.filter (String.IsNullOrWhiteSpace >> not) }
|> Ok


let removeDomain state domainId =
match state with
| Existing domain ->
Expand All @@ -225,14 +234,14 @@ module Domain =
let private asList item = item |> Result.map List.singleton
let decide (command: Command) (state: State) =
match command with
| CreateDomain (domainId, createDomain) -> newDomain domainId createDomain.Name None |> asList
| CreateDomain (domainId, createDomain) -> newDomain domainId createDomain.Name createDomain.ShortName createDomain.Vision None
| CreateSubdomain (domainId, subdomainId, createDomain) ->
newDomain domainId createDomain.Name (Some subdomainId) |> asList
newDomain domainId createDomain.Name createDomain.ShortName createDomain.Vision (Some subdomainId)
| RemoveDomain domainId -> removeDomain state domainId
| MoveDomain (domainId, move) -> moveDomain state move.ParentDomainId domainId
| RenameDomain (domainId, rename) -> renameDomain rename.Name domainId state |> asList
| RefineVision (domainId, refineVision) -> refineVisionOfDomain refineVision.Vision domainId |> asList
| AssignShortName (domainId, assignShortName) -> assignShortNameToDomain assignShortName.ShortName domainId |> asList
| RefineVision (domainId, refineVision) -> refineVisionOfDomain (refineVision.Vision |> Option.ofObj) domainId |> asList
| AssignShortName (domainId, assignShortName) -> assignShortNameToDomain (assignShortName.ShortName |> Option.ofObj) domainId |> asList



5 changes: 5 additions & 0 deletions frontend-vue/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"bounded_context_canvas.description.empty": "no description written yet",
"bounded_context_canvas.description.error.update": "Could not update bounded context description",
"bounded_context_canvas.description.title": "Description",
"bounded_context_canvas.description.description": "A few sentences describing the why and what of the context in business language. No technical details here.",
"bounded_context_canvas.domain_roles.actions.collapsed.add": "add new domain role",
"bounded_context_canvas.domain_roles.actions.collapsed.choose": "choose domain role from pre-defined list",
"bounded_context_canvas.domain_roles.actions.open.add": "add domain role",
Expand Down Expand Up @@ -261,10 +262,14 @@
"domains.search.title": "All Domains Search",
"domains.modal.create.error.submit": "Could not create new domain",
"domains.modal.create.form.fields.name.label": "Name of the Domain",
"domains.modal.create.form.fields.short_name.label": "Short name",
"domains.modal.create.form.fields.vision.label": "Vision",
"domains.modal.create.form.submit": "create domain",
"domains.modal.create.title": "Create a new Domain",
"domains.modal.create_bounded_context.error.submit": "Could not create new bounded context",
"domains.modal.create_bounded_context.form.fields.name.label": "Name of the Bounded Context",
"domains.modal.create_bounded_context.form.fields.short_name.label": "Short Key",
"domains.modal.create_bounded_context.form.fields.description.label": "Description",
"domains.modal.create_bounded_context.form.submit": "create bounded context",
"domains.modal.create_bounded_context.title": "Create Bounded Context",
"domains.modal.create_subdomain.error.submit": "Could not create new subdomain",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
<ContextureTextarea
v-model="description"
name="description"
description="A few sentences describing the why and what of the context in business language. No technical details here."
:description="t('bounded_context_canvas.description.description')"
:rules="requiredString"
required
/>
<ContexturePrimaryButton :label="t('common.save')" class="mt-4" size="sm" @click="onUpdate">
<template #left>
Expand All @@ -35,6 +37,8 @@
import { storeToRefs } from "pinia";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import * as zod from "zod";
import { toFieldValidator } from "@vee-validate/zod";
import ContextureBoundedContextCanvasElement from "~/components/bounded-context/canvas/ContextureBoundedContextCanvasElement.vue";
import ContextureHelpfulErrorAlert from "~/components/primitives/alert/ContextureHelpfulErrorAlert.vue";
import ContexturePrimaryButton from "~/components/primitives/button/ContexturePrimaryButton.vue";
Expand All @@ -52,6 +56,7 @@ const { canModify } = useAuthStore();
const description = ref(activeBoundedContext.value.description);
const submitError = ref();
const editMode = ref(false);
const requiredString = toFieldValidator(zod.string().min(1, t("validation.required")));
async function onUpdate() {
submitError.value = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
name="name"
:description="t('bounded_context_canvas.edit.form.description.name')"
:label="t('bounded_context_canvas.edit.form.label.name')"
:rules="nameValidationSchema"
:rules="requiredString"
required
/>

Expand Down Expand Up @@ -46,7 +46,7 @@ const emit = defineEmits(["submit"]);
const { t } = useI18n();
const editModel: Ref<BoundedContext> = toRef(props, "initialValue");
const nameValidationSchema = toFieldValidator(zod.string().min(1, t("validation.required")));
const requiredString = toFieldValidator(zod.string().min(1, t("validation.required")));
function submit(values: any) {
emit("submit", values);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
name="key"
:rules="shortNameValidationRules"
@update:model-value="onChange"
required
/>
</template>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { describe, expect, test } from "vitest";
import { shortNameValidationSchema } from "~/components/core/change-short-name/changeShortNameValidationSchema";

describe("change short name validation rules", () => {
test.each(["short-name", "a1", "", null, undefined])("'%s' is valid short name", (shortName) => {
test.each(["", null, undefined])("'%s' can not be null or empty", (shortName) => {
const validation = shortNameValidationSchema("", [], []);

const { success } = validation.safeParse(shortName);

expect(success).toBeTruthy();
expect(success).toBeFalsy();
});

test("cannot exceed 16 characters", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const shortNameValidationSchema = (
) =>
zod
.string()
.min(1)
.max(16)
.superRefine((arg: string, ctx: RefinementCtx) => {
isUniqueIn<Domain>(arg, ctx, {
Expand Down Expand Up @@ -43,10 +44,7 @@ export const shortNameValidationSchema = (
.refine((term: string) => !endsWith(term, "-"), { message: "Must not end with hyphen" })
.refine((term: string) => term.split("").every((c) => allowedCharacters.includes(c)), {
message: "Must only contain alphanumeric characters and hyphens",
})
.nullable()
.optional()
.or(zod.literal(""));
});

function mapDomain(prop: string, domains: Domain[]): string {
const domain = domains.find((d) => d.shortName?.toUpperCase() === prop.toUpperCase());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ const form: DynamicFormSchema<CreateDomain> = {
component: ContextureInputText,
componentProps: {
label: t("domains.modal.create.form.fields.name.label"),
description: t("domains.details.edit.form.description.name"),
required: true,
rules: toFieldValidator(zod.string().min(1)),
},
},
{
name: "shortName",
component: ContextureInputText,
componentProps: {
label: t("domains.modal.create.form.fields.short_name.label"),
description: t("domains.details.edit.form.description.key"),
required: true,
rules: toFieldValidator(zod.string().min(1)),
},
},
{
name: "vision",
component: ContextureInputText,
componentProps: {
label: t("domains.modal.create.form.fields.vision.label"),
description: t("domains.details.edit.form.description.vision"),
required: true,
rules: toFieldValidator(zod.string().min(1)),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import ContextureModal from "~/components/primitives/modal/ContextureModal.vue";
import { useBoundedContextsStore } from "~/stores/boundedContexts";
import { Domain } from "~/types/domain";
import { CreateBoundedContext } from "~/types/boundedContext";
import ContextureChangeKey from "~/components/core/change-short-name/ContextureChangeShortName.vue";
interface Props {
isOpen: boolean;
Expand All @@ -53,6 +54,25 @@ const form: DynamicFormSchema<CreateBoundedContext> = {
component: ContextureInputText,
componentProps: {
label: t("domains.modal.create_bounded_context.form.fields.name.label"),
description: t("bounded_context_canvas.edit.form.description.name"),
required: true,
rules: toFieldValidator(zod.string().min(1)),
},
},
{
name: "shortName",
component: ContextureChangeKey,
componentProps: {
label: t("domains.modal.create_bounded_context.form.fields.short_name.label"),
description: t("bounded_context_canvas.edit.form.description.key"),
},
},
{
name: "description",
component: ContextureInputText,
componentProps: {
label: t("domains.modal.create_bounded_context.form.fields.description.label"),
description: t("bounded_context_canvas.description.description"),
required: true,
rules: toFieldValidator(zod.string().min(1)),
},
Expand Down
Loading

0 comments on commit 87d30a2

Please sign in to comment.