Skip to content
Snippets Groups Projects
Commit 08f5e611 authored by jensilo's avatar jensilo
Browse files

eiffel elicitation template get route

parent 2575cae3
No related branches found
No related tags found
No related merge requests found
......@@ -61,7 +61,7 @@ type BasicRule struct {
// Type is the type of the rule. It is used to determine the rules value and how to validate it.
// Check further documentation for all valid types that are supported by the EIFFEL basic template (EBT).
Type string `json:"type" hvalidate:"required"`
// Hint is an optional description to hint the user of the template into correct usage of the template according to the rule(s).
// Hint is an optional, short description to hint the user of the template into correct usage of the template according to the rule(s).
Hint string `json:"hint"`
// Explanation can optionally be defined to further explain the use/parsing of the rule to the user of the template.
Explanation string `json:"explanation"`
......@@ -141,6 +141,8 @@ type RuleParser interface {
Parse(ctx context.Context, rule BasicRule, segment parser.ParsingSegment) ([]parser.ParsingLog, error)
// Validate validates that a rule, defined in a template, is valid. This could for example validate that the rule's value is of the correct data type.
Validate(v validation.V, rule BasicRule) []error
// DisplayType returns the display type of the rule. This is used to determine which input field or other UI element to render for the rule.
DisplayType(rule BasicRule) TemplateDisplayType
}
// EqualsRuleParser is a rule parser for the rule type 'equals'.
......@@ -435,6 +437,11 @@ func (p EqualsRuleParser) Validate(v validation.V, rule BasicRule) []error {
return []error{RuleInvalidValueError{Rule: &rule}}
}
// DisplayType implements the RuleParser interface for the EqualsRuleParser. Equals rules are arbitrary strings displayed as text.
func (p EqualsRuleParser) DisplayType(rule BasicRule) TemplateDisplayType {
return TemplateDisplayString
}
// Parse implements the RuleParser interface for the EqualsAnyRuleParser. It is used to parse rules of the type 'equalsAny'.
// The equalsAny rule expects a slice of strings as value, converts each string to lowercase and compares it to the lowercase segment's value.
// If any of the values are equal, no parsing error is reported.
......@@ -472,6 +479,11 @@ func (p EqualsAnyRuleParser) Validate(v validation.V, rule BasicRule) []error {
return []error{RuleInvalidValueError{Rule: &rule}}
}
// DisplayType implements the RuleParser interface for the EqualsAnyRuleParser. EqualsAny rules are input fields with a single select datalist.
func (p EqualsAnyRuleParser) DisplayType(rule BasicRule) TemplateDisplayType {
return TemplateDisplayInputTypeSingleSelect
}
// Parse implements the RuleParser interface for the PlaceholderRuleParser. It is used to parse rules of the type 'placeholder'.
func (p PlaceholderRuleParser) Parse(ctx context.Context, rule BasicRule, segment parser.ParsingSegment) ([]parser.ParsingLog, error) {
return nil, nil
......@@ -482,6 +494,23 @@ func (p PlaceholderRuleParser) Validate(v validation.V, rule BasicRule) []error
return nil
}
// DisplayType implements the RuleParser interface for the PlaceholderRuleParser. Placeholder rules are input fields with a text type.
// The size of the input field is determined by the rule's size property. Large and full size will be rendered as a textarea.
func (p PlaceholderRuleParser) DisplayType(rule BasicRule) TemplateDisplayType {
switch rule.Size {
case "small":
return TemplateDisplayInputTypeText
case "medium":
return TemplateDisplayInputTypeText
case "large":
return TemplateDisplayInputTypeTextarea
case "full":
return TemplateDisplayInputTypeTextarea
default:
return TemplateDisplayInputTypeText
}
}
// prepareSegments prepares segments by trimming whitespaces from the input string and indexing them.
func prepareSegments(segments []parser.ParsingSegment) map[string]parser.ParsingSegment {
indexedSegments := make(map[string]parser.ParsingSegment, len(segments))
......
package eiffel
import (
"encoding/json"
"github.com/org-harmony/harmony/src/app/template"
"github.com/org-harmony/harmony/src/core/validation"
)
// TemplateDisplayTypes returns a map of rule names to display types. The rule names are the keys of the BasicTemplate.Rules map.
// This can be used in the eiffel.TemplateFormData`.DisplayTypes field.
func TemplateDisplayTypes(bt *BasicTemplate, ruleParsers *RuleParserProvider) map[string]TemplateDisplayType {
displayTypes := map[string]TemplateDisplayType{}
for ruleName, rule := range bt.Rules {
ruleParser, err := ruleParsers.Parser(rule.Type)
if err != nil {
continue
}
displayType := ruleParser.DisplayType(rule)
if displayType == "" {
continue
}
displayTypes[ruleName] = displayType
}
return displayTypes
}
// TemplateIntoBasicTemplate parses a templates config into a BasicTemplate and validates it.
// If unmarshalling the config into the BasicTemplate fails or validation fails, an error is returned.
func TemplateIntoBasicTemplate(t *template.Template, validator validation.V, ruleParsers *RuleParserProvider) (*BasicTemplate, error) {
ebt := &BasicTemplate{}
err := json.Unmarshal([]byte(t.Config), ebt)
if err != nil {
return nil, err
}
errs := ebt.Validate(validator, ruleParsers)
if len(errs) > 0 {
return nil, template.ErrInvalidTemplate
}
return ebt, nil
}
......@@ -2,16 +2,48 @@ package eiffel
import (
"encoding/json"
"errors"
"github.com/google/uuid"
"github.com/org-harmony/harmony/src/app/template"
"github.com/org-harmony/harmony/src/app/user"
"github.com/org-harmony/harmony/src/core/event"
"github.com/org-harmony/harmony/src/core/hctx"
"github.com/org-harmony/harmony/src/core/util"
"github.com/org-harmony/harmony/src/core/validation"
"github.com/org-harmony/harmony/src/core/web"
"net/http"
"strings"
)
const (
// TemplateDisplayString will display the rule value as text.
TemplateDisplayString TemplateDisplayType = "text"
// TemplateDisplayInputTypeText will display the rule value as a text input.
TemplateDisplayInputTypeText TemplateDisplayType = "input-text"
// TemplateDisplayInputTypeTextarea will display the rule value as a textarea.
TemplateDisplayInputTypeTextarea TemplateDisplayType = "input-textarea"
// TemplateDisplayInputTypeSingleSelect will display the rule value as an input field with datalist and single select.
TemplateDisplayInputTypeSingleSelect TemplateDisplayType = "input-single-select"
)
var (
// ErrTemplateNotFound will be displayed to the user if the template could not be found.
ErrTemplateNotFound = errors.New("eiffel.elicitation.template.not-found")
// ErrTemplateVariantNotFound will be displayed to the user if the template variant could not be found.
ErrTemplateVariantNotFound = errors.New("eiffel.elicitation.template.variant.not-found")
)
// TemplateDisplayType specifies how a rule should be displayed in the UI.
type TemplateDisplayType string
// TemplateFormData is the data that is passed to the template rendering the elicitation form.
type TemplateFormData struct {
Template *BasicTemplate
// DisplayTypes is a map of rule names to display types. The rule names are the keys of the BasicTemplate.Rules map.
// The display types are used to determine how the rule should be displayed in the UI.
DisplayTypes map[string]TemplateDisplayType
}
func RegisterController(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
// TODO move this to module init when module manager is implemented (see subscribeEvents)
subscribeEvents(appCtx)
......@@ -75,7 +107,7 @@ func registerNavigation(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
func eiffelElicitationPage(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
return web.NewController(appCtx, webCtx, func(io web.IO) error {
return io.Render(
nil,
web.NewFormData(TemplateFormData{}, nil),
"eiffel.elicitation.page",
"eiffel/elicitation-page.go.html",
"eiffel/elicitation-template.go.html",
......@@ -92,9 +124,35 @@ func searchModal(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
}
func elicitationTemplate(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
templateRepository := util.UnwrapType[template.Repository](appCtx.Repository(template.RepositoryName))
return web.NewController(appCtx, webCtx, func(io web.IO) error {
templateID := web.URLParam(io.Request(), "templateID")
variant := web.URLParam(io.Request(), "variant")
templateUUID, err := uuid.Parse(templateID)
if err != nil {
return io.InlineError(ErrTemplateNotFound, err)
}
tmpl, err := templateRepository.FindByID(io.Context(), templateUUID)
if err != nil {
return io.InlineError(ErrTemplateNotFound, err)
}
bt, err := TemplateIntoBasicTemplate(tmpl, appCtx.Validator, RuleParsers())
if err != nil {
return io.InlineError(err)
}
if _, ok := bt.Variants[variant]; !ok {
return io.InlineError(ErrTemplateVariantNotFound)
}
displayTypes := TemplateDisplayTypes(bt, RuleParsers())
return io.Render(
nil,
web.NewFormData(TemplateFormData{Template: bt, DisplayTypes: displayTypes}, nil),
"eiffel.elicitation.template",
"eiffel/elicitation-template.go.html",
"eiffel/_form-elicitation.go.html",
......
{{ define "eiffel.elicitation.template" }}
{{ if .Data.Form.Template }}
<div class="accordion mt-3 eiffel-elicitation-template-info" id="eiffelTemplateInfoAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
......@@ -74,4 +75,9 @@
<div class="eiffel-elicitation-template-variant-form mt-3 w-100">
{{ template "eiffel.elicitation.form" . }}
</div>
{{ else }}
<div class="alert alert-info mt-3" role="alert">
{{ t "eiffel.elicitation.template.search.call-to-action" }}
</div>
{{ end }}
{{ end }}
\ No newline at end of file
......@@ -85,22 +85,26 @@
"parser": {
"error": {
"invalid-template": "Die EIFFEL Schablone ist ungültig. Bitte überprüfen Sie die Schablonen-Dokumentation.",
"missing-rule": "In der Variante {{ .variant }} der Schablone {{ .template }} wird versucht eine Regel {{ .rule }} zu benutzen, die nicht definiert wird.",
"missing-rule": "In der Variante {{ .variant }} der Schablone {{ .template }} wird versucht eine Regel \"{{ .rule }}\" zu benutzen, die nicht definiert wird.",
"invalid-variant": "Die Variante ist ungültig.",
"missing-rule-parser": "Der geforderte Parser für die Regel ist nicht verfügbar. Wahrscheinlich ist der Regel-Typ {{ .ruleType }} nicht korrekt. Bitte überprüfen Sie die Schablonen-Dokumentation.",
"missing-segment": "Eine Eingabe für die Regel {{ .name }} ({{ .technicalName }}) fehlt.",
"invalid-rule-value": "Der Wert \"value\" für die Regel {{ .rule }} vom Typ {{ .type }} ist ungültig. Bitte überprüfen Sie die Schablonen-Dokumentation."
"missing-rule-parser": "Der geforderte Parser für die Regel ist nicht verfügbar. Wahrscheinlich ist der Regel-Typ \"{{ .type }}\" nicht korrekt. Bitte überprüfen Sie die Schablonen-Dokumentation.",
"missing-segment": "Eine Eingabe für die Regel \"{{ .name }}\" ({{ .technicalName }}) fehlt.",
"invalid-rule-value": "Der Wert \"value\" für die Regel \"{{ .rule }}\" vom Typ \"{{ .type }}\" ist ungültig. Bitte überprüfen Sie die Schablonen-Dokumentation."
}
},
"elicitation": {
"template": {
"search": {
"title": "Schablone suchen",
"shortcut": "Strg + F"
"shortcut": "Strg + F",
"call-to-action": "Es wurde noch keine Schablone ausgewählt. Bitte nutzen Sie die Suche, um eine Schablone zu finden.",
"not-found": "Keine Schablone gefunden."
},
"not-found": "Die Schablone wurde nicht gefunden.",
"variant": {
"left.shortcut": "<- + Strg",
"right.shortcut": "Strg + ->"
"right.shortcut": "Strg + ->",
"not-found": "Die Variante wurde nicht gefunden."
},
"form": {
"title": "Anforderung erfassen"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment