Skip to content
Snippets Groups Projects
Commit bf294207 authored by Danis Kirasirov's avatar Danis Kirasirov :8ball: Committed by Pavel Antonov
Browse files

feat(core): Реализована возможность загрузки схем из YAML и JSON файлов

Close #PRXS-2731
parent 8f8f8dcd
No related branches found
No related tags found
No related merge requests found
...@@ -62,8 +62,8 @@ require ( ...@@ -62,8 +62,8 @@ require (
go.opentelemetry.io/otel/metric v1.24.0 go.opentelemetry.io/otel/metric v1.24.0
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sync v0.6.0 // indirect golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.15.0
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
......
...@@ -151,8 +151,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w ...@@ -151,8 +151,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
......
...@@ -2,6 +2,8 @@ package schema ...@@ -2,6 +2,8 @@ package schema
import ( import (
"context" "context"
"os"
"path/filepath"
"reflect" "reflect"
"git.perx.ru/perxis/perxis-go/pkg/errors" "git.perx.ru/perxis/perxis-go/pkg/errors"
...@@ -25,6 +27,41 @@ func NewFromField(f *field.Field) *Schema { ...@@ -25,6 +27,41 @@ func NewFromField(f *field.Field) *Schema {
return &Schema{Field: *f} return &Schema{Field: *f}
} }
// FromFile инициализирует и возвращает объект схемы из файла
// Поддерживаются форматы JSON и YAML
func FromFile(path string) (*Schema, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
switch filepath.Ext(path) {
case ".json":
return FromJSON(file)
case ".yaml", ".yml":
return FromYAML(file)
}
return nil, errors.New("schema must be in JSON or YAML format")
}
// FromFiles возвращает все валидные схемы в переданной директории
func FromFiles(path string) ([]*Schema, error) {
files, err := os.ReadDir(path)
if err != nil {
return nil, err
}
result := make([]*Schema, 0, len(files))
for _, file := range files {
if schema, err := FromFile(filepath.Join(path, file.Name())); err == nil {
result = append(result, schema)
}
}
return result, nil
}
var ( var (
Encode = field.Encode Encode = field.Encode
Decode = field.Decode Decode = field.Decode
......
package schema package schema
import ( import (
"io"
"git.perx.ru/perxis/perxis-go/pkg/errors" "git.perx.ru/perxis/perxis-go/pkg/errors"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
) )
func FromJSON(r io.Reader) (s *Schema, err error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
s = New()
err = s.UnmarshalJSON(data)
return s, err
}
type jsonSchema struct { type jsonSchema struct {
Loaded bool `json:"loaded"` Loaded bool `json:"loaded"`
Metadata map[string]string `json:"metadata"` Metadata map[string]string `json:"metadata"`
...@@ -23,15 +36,6 @@ func (s *Schema) UnmarshalJSON(b []byte) error { ...@@ -23,15 +36,6 @@ func (s *Schema) UnmarshalJSON(b []byte) error {
return err return err
} }
//if len(j.Field) > 0 {
// if err := s.Field.UnmarshalJSON(j.Field); err != nil {
// return err
// }
// //if err := jsoniter.Unmarshal(j.Field, &s.Field); err != nil {
// // return err
// //}
//}
return nil return nil
} }
......
package schema
import (
"io"
jsoniter "github.com/json-iterator/go"
"gopkg.in/yaml.v3"
)
func FromYAML(r io.Reader) (s *Schema, err error) {
yml, err := io.ReadAll(r)
if err != nil {
return nil, err
}
s = New()
err = s.UnmarshalYAML(yml)
return s, err
}
func (s *Schema) UnmarshalYAML(b []byte) error {
jsonData, err := yamlToJson(b)
if err != nil {
return err
}
return s.UnmarshalJSON(jsonData)
}
func (s *Schema) MarshalYAML() ([]byte, error) {
jsonData, err := s.MarshalJSON()
if err != nil {
return nil, err
}
return jsonToYaml(jsonData)
}
func jsonToYaml(b []byte) ([]byte, error) {
var data interface{}
if err := jsoniter.Unmarshal(b, &data); err != nil {
return nil, err
}
return yaml.Marshal(data)
}
func yamlToJson(b []byte) ([]byte, error) {
var data interface{}
if err := yaml.Unmarshal(b, &data); err != nil {
return nil, err
}
return jsoniter.Marshal(data)
}
{
"ui": {
"widget": "Tabs",
"options": {
"description": "path",
"collection_icon": "ApartmentOutlined/FileTextOutlined",
"fields": [
"content",
"seo",
"settings",
"advanced",
"design",
"variables"
],
"title": "name",
"key": "path"
},
"list_view": {
"options": {
"sort": [
"path"
],
"page_size": 50,
"fields": [
"path",
"name",
"updated_at",
"updated_by",
"state"
]
}
}
},
"includes": [
{
"ref": "hoop_item_options",
"optional": true
},
{
"ref": "ext_web_pages_*",
"optional": true
}
],
"type": "object",
"params": {
"inline": false,
"fields": {
"design": {
"title": "Дизайн",
"includes": [
{
"ref": "web_design"
}
],
"type": "object",
"params": {
"inline": false,
"fields": {}
}
},
"variables": {
"title": "Переменные",
"ui": {
"options": {
"fields": [
"variables"
]
}
},
"type": "object",
"params": {
"inline": true,
"fields": {
"variables": {
"type": "array",
"params": {
"item": {
"ui": {
"options": {
"collection_icon": "SettingOutlined/FileExcelOutlined",
"fields": [
"id",
"name",
"value"
]
},
"list_view": {
"options": {
"fields": [
"id",
"name",
"updated_at",
"updated_by",
"state"
],
"page_size": 50
}
}
},
"type": "object",
"params": {
"inline": false,
"fields": {
"id": {
"title": "Идентификатор переменной",
"text_search": true,
"options": {
"required": true
},
"type": "string",
"params": {}
},
"name": {
"title": "Название переменной",
"text_search": true,
"type": "string",
"params": {}
},
"value": {
"title": "Значение переменной",
"type": "string",
"params": {}
}
}
}
}
}
}
}
}
},
"content": {
"title": "Содержимое страницы",
"ui": {
"options": {
"fields": [
"blocks"
]
}
},
"type": "object",
"params": {
"inline": true,
"fields": {
"blocks": {
"title": "Блоки",
"ui": {
"widget": "BlockList",
"options": {
"create": {
"classes": [
"class_web_blocks"
]
}
}
},
"type": "array",
"params": {
"item": {
"title": "Блок",
"type": "reference",
"params": {
"allowedCollections": [
"web_block_*"
]
}
}
}
}
}
}
},
"seo": {
"title": "SEO",
"includes": [
{
"ref": "web_seo"
}
],
"type": "object",
"params": {
"inline": false,
"fields": {}
}
},
"settings": {
"title": "Настройки",
"ui": {
"options": {
"fields": [
"name",
"parent",
"path",
"slug",
"section",
"datasource",
"redirect",
"redirect_path",
"redirect_url",
"outputs",
"navTitle",
"navHide",
"weight"
]
}
},
"type": "object",
"params": {
"inline": true,
"fields": {
"path": {
"title": "Путь",
"unique": true,
"text_search": true,
"options": {
"value": "$perxis.Item.Template == true ? _value : parent == nil ? '/' : slug == nil ? make_path(parent, slugify(replace_markers(name))) : make_path(parent, slugify(replace_markers(slug)))"
},
"type": "string",
"params": {}
},
"redirect_url": {
"title": "URL для перехода",
"condition": "redirect == true",
"type": "string",
"params": {}
},
"outputs": {
"title": "Форматы страницы для вывода",
"type": "array",
"params": {
"item": {
"ui": {
"widget": "Lookup",
"options": {
"allowedCollections": [
{
"collection": "web_outputs"
}
]
}
},
"type": "string",
"params": {}
}
}
},
"weight": {
"title": "Порядок следования",
"ui": {
"widget": "NumberInput"
},
"type": "number",
"params": {
"format": "int"
}
},
"navHide": {
"title": "Скрыть страницу из навигации",
"ui": {
"widget": "Checkbox"
},
"type": "bool",
"params": {}
},
"section": {
"title": "Раздел",
"ui": {
"widget": "Checkbox"
},
"type": "bool",
"params": {}
},
"parent": {
"title": "Родительский раздел",
"description": "Раздел сайта, где расположена страница",
"ui": {
"widget": "Lookup",
"options": {
"allowedCollections": [
{
"collection": "web_pages"
}
]
}
},
"type": "string",
"params": {}
},
"name": {
"title": "Название",
"indexed": true,
"text_search": true,
"options": {
"required": true,
"value": "$perxis.Item.Template == true ? _value : replace_markers(_value)"
},
"type": "string",
"params": {}
},
"navTitle": {
"title": "Название для навигации",
"type": "string",
"params": {}
},
"redirect_path": {
"title": "Страница для перехода",
"ui": {
"widget": "Lookup",
"options": {
"allowedCollections": [
{
"collection": "web_pages"
}
]
}
},
"condition": "redirect == true",
"type": "string",
"params": {}
},
"datasource": {
"title": "Источник данных",
"description": "Источник данных из которого будут формироваться подстраницы раздела",
"ui": {
"widget": "Lookup",
"options": {
"allowedCollections": [
{
"collection": "web_datasources"
}
]
}
},
"condition": "section == true",
"type": "string",
"params": {}
},
"redirect": {
"title": "Перенаправление",
"description": "Страница не имеет содержимого и перенаправляет посетителей на другой адрес",
"ui": {
"widget": "Checkbox"
},
"type": "bool",
"params": {}
},
"slug": {
"title": "Slug",
"description": "Идентификатор страницы в адресе URL",
"text_search": true,
"options": {
"value": "$perxis.Item.Template == true ? _value : parent == nil ? nil : _value == nil ? slugify(replace_markers(name)) : slugify(replace_markers(_value))"
},
"type": "string",
"params": {}
}
}
}
},
"advanced": {
"title": "Расширенные настройки",
"ui": {
"options": {
"fields": [
"scripts"
]
}
},
"type": "object",
"params": {
"inline": false,
"fields": {
"scripts": {
"title": "Дополнительные скрипты",
"type": "array",
"params": {
"item": {
"ui": {
"options": {
"fields": [
"src",
"type",
"content"
]
}
},
"type": "object",
"params": {
"inline": false,
"fields": {
"type": {
"title": "Media Type скрипта",
"type": "string",
"params": {}
},
"content": {
"title": "Содержимое скрипта",
"type": "string",
"params": {}
},
"src": {
"title": "URL для загрузки скрипта",
"type": "string",
"params": {}
}
}
}
}
}
}
}
}
}
}
},
"loaded": false,
"metadata": {
"extension": "perxisweb"
}
}
\ No newline at end of file
---
ui:
widget: Tabs
options:
description: path
collection_icon: ApartmentOutlined/FileTextOutlined
fields:
- content
- seo
- settings
- advanced
- design
- variables
title: name
key: path
list_view:
options:
sort:
- path
page_size: 50
fields:
- path
- name
- updated_at
- updated_by
- state
includes:
- ref: hoop_item_options
optional: true
- ref: ext_web_pages_*
optional: true
type: object
params:
inline: false
fields:
design:
title: Дизайн
includes:
- ref: web_design
type: object
params:
inline: false
fields: {}
variables:
title: Переменные
ui:
options:
fields:
- variables
type: object
params:
inline: true
fields:
variables:
type: array
params:
item:
ui:
options:
collection_icon: SettingOutlined/FileExcelOutlined
fields:
- id
- name
- value
list_view:
options:
fields:
- id
- name
- updated_at
- updated_by
- state
page_size: 50
type: object
params:
inline: false
fields:
id:
title: Идентификатор переменной
text_search: true
options:
required: true
type: string
params: {}
name:
title: Название переменной
text_search: true
type: string
params: {}
value:
title: Значение переменной
type: string
params: {}
content:
title: Содержимое страницы
ui:
options:
fields:
- blocks
type: object
params:
inline: true
fields:
blocks:
title: Блоки
ui:
widget: BlockList
options:
create:
classes:
- class_web_blocks
type: array
params:
item:
title: Блок
type: reference
params:
allowedCollections:
- web_block_*
seo:
title: SEO
includes:
- ref: web_seo
type: object
params:
inline: false
fields: {}
settings:
title: Настройки
ui:
options:
fields:
- name
- parent
- path
- slug
- section
- datasource
- redirect
- redirect_path
- redirect_url
- outputs
- navTitle
- navHide
- weight
type: object
params:
inline: true
fields:
path:
title: Путь
unique: true
text_search: true
options:
value: "$perxis.Item.Template == true ? _value : parent == nil ? '/'
: slug == nil ? make_path(parent, slugify(replace_markers(name)))
: make_path(parent, slugify(replace_markers(slug)))"
type: string
params: {}
redirect_url:
title: URL для перехода
condition: redirect == true
type: string
params: {}
outputs:
title: Форматы страницы для вывода
type: array
params:
item:
ui:
widget: Lookup
options:
allowedCollections:
- collection: web_outputs
type: string
params: {}
weight:
title: Порядок следования
ui:
widget: NumberInput
type: number
params:
format: int
navHide:
title: Скрыть страницу из навигации
ui:
widget: Checkbox
type: bool
params: {}
section:
title: Раздел
ui:
widget: Checkbox
type: bool
params: {}
parent:
title: Родительский раздел
description: Раздел сайта, где расположена страница
ui:
widget: Lookup
options:
allowedCollections:
- collection: web_pages
type: string
params: {}
name:
title: Название
indexed: true
text_search: true
options:
required: true
value: "$perxis.Item.Template == true ? _value : replace_markers(_value)"
type: string
params: {}
navTitle:
title: Название для навигации
type: string
params: {}
redirect_path:
title: Страница для перехода
ui:
widget: Lookup
options:
allowedCollections:
- collection: web_pages
condition: redirect == true
type: string
params: {}
datasource:
title: Источник данных
description: Источник данных из которого будут формироваться подстраницы
раздела
ui:
widget: Lookup
options:
allowedCollections:
- collection: web_datasources
condition: section == true
type: string
params: {}
redirect:
title: Перенаправление
description: Страница не имеет содержимого и перенаправляет посетителей
на другой адрес
ui:
widget: Checkbox
type: bool
params: {}
slug:
title: Slug
description: Идентификатор страницы в адресе URL
text_search: true
options:
value: "$perxis.Item.Template == true ? _value : parent == nil ? nil
: _value == nil ? slugify(replace_markers(name)) : slugify(replace_markers(_value))"
type: string
params: {}
advanced:
title: Расширенные настройки
ui:
options:
fields:
- scripts
type: object
params:
inline: false
fields:
scripts:
title: Дополнительные скрипты
type: array
params:
item:
ui:
options:
fields:
- src
- type
- content
type: object
params:
inline: false
fields:
type:
title: Media Type скрипта
type: string
params: {}
content:
title: Содержимое скрипта
type: string
params: {}
src:
title: URL для загрузки скрипта
type: string
params: {}
loaded: false
metadata:
extension: perxisweb
package test
import (
"testing"
"git.perx.ru/perxis/perxis-go/pkg/extension"
"git.perx.ru/perxis/perxis-go/pkg/references"
"git.perx.ru/perxis/perxis-go/pkg/schema"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
"git.perx.ru/perxis/perxis-go/pkg/schema/modify"
"git.perx.ru/perxis/perxis-go/pkg/schema/validate"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFromFile(t *testing.T) {
for _, tt := range []struct {
path string
wantSchema *schema.Schema
wantErr string
}{
{
path: "assets/not_schema.txt",
wantErr: "schema must be in JSON or YAML format",
},
{
path: "non/existent.json",
wantErr: "open non/existent.json: no such file or directory",
},
{
path: "assets/invalid.json",
wantErr: "error unmarshal json into field",
},
{
path: "assets/web_pages.json",
wantSchema: getPagesSchema(),
},
{
path: "assets/web_pages.yml",
wantSchema: getPagesSchema(),
},
} {
t.Run(tt.path, func(t *testing.T) {
result, err := schema.FromFile(tt.path)
if tt.wantErr != "" {
require.Error(t, err)
assert.ErrorContains(t, err, tt.wantErr)
return
}
require.NoError(t, err)
require.Equal(t, tt.wantSchema, result)
})
}
}
func TestFromFiles(t *testing.T) {
t.Run("Non-existen path", func(t *testing.T) {
schemas, err := schema.FromFiles("non-existen")
require.Error(t, err)
require.ErrorContains(t, err, "no such file or directory")
require.Nil(t, schemas)
})
t.Run("Success", func(t *testing.T) {
schemas, err := schema.FromFiles("assets")
require.NoError(t, err)
require.Len(t, schemas, 2, "В директории хранятся две корректные схемы")
require.Equal(t, schemas[0], schemas[1], "Они одинаковые, но в разных форматах")
})
}
// Оригинальное объявление схемы Web/Страницы
// Значение констант подставлено вручную
func getPagesSchema() *schema.Schema {
content := field.Object(
true, "blocks", field.Array(
references.Field([]string{"web_block_*"}).SetTitle("Блок"),
).
SetTitle("Блоки").
WithUI(&field.UI{
Widget: "BlockList",
Options: map[string]interface{}{
"create": map[string]interface{}{
"classes": []string{"class_web_blocks"},
},
},
}),
).SetTitle("Содержимое страницы")
// SEO
seo := field.Object().WithIncludes("web_seo").SetTitle("SEO")
//Settings
settings := field.Object(true,
"name", field.String(
validate.Required(),
modify.Value("$perxis.Item.Template == true ? _value : replace_markers(_value)"),
).SetTitle("Название").SetTextSearch(true).SetIndexed(true),
"parent", field.String().SetTitle("Родительский раздел").SetDescription("Раздел сайта, где расположена страница").WithUI(&field.UI{
Widget: "Lookup",
Options: map[string]interface{}{
"allowedCollections": []interface{}{
map[string]interface{}{"collection": "web_pages"},
},
},
}), // TODO lookup "section == true"
"path", field.String(
modify.Value("$perxis.Item.Template == true ? _value : parent == nil ? '/' : slug == nil ? make_path(parent, slugify(replace_markers(name))) : make_path(parent, slugify(replace_markers(slug)))"),
).SetTitle("Путь").SetUnique(true).SetTextSearch(true), // TODO readonly
"slug", field.String(modify.Value("$perxis.Item.Template == true ? _value : parent == nil ? nil : _value == nil ? slugify(replace_markers(name)) : slugify(replace_markers(_value))")).SetTitle("Slug").SetDescription("Идентификатор страницы в адресе URL").SetTextSearch(true),
"section", field.Bool().SetTitle("Раздел").WithUI(&field.UI{Widget: "Checkbox"}),
"datasource", field.String().WithUI(&field.UI{
Widget: "Lookup",
Options: map[string]interface{}{
"allowedCollections": []interface{}{
map[string]interface{}{"collection": "web_datasources"},
},
},
}).SetTitle("Источник данных").SetDescription("Источник данных из которого будут формироваться подстраницы раздела").SetCondition("section == true"),
"redirect", field.Bool().SetTitle("Перенаправление").SetDescription("Страница не имеет содержимого и перенаправляет посетителей на другой адрес").WithUI(&field.UI{Widget: "Checkbox"}),
"redirect_path", field.String().SetTitle("Страница для перехода").WithUI(&field.UI{
Widget: "Lookup",
Options: map[string]interface{}{
"allowedCollections": []interface{}{
map[string]interface{}{"collection": "web_pages"},
},
},
}).SetCondition("redirect == true"),
"redirect_url", field.String().SetTitle("URL для перехода").SetCondition("redirect == true"),
"outputs", field.Array(field.String().WithUI(&field.UI{
Widget: "Lookup",
Options: map[string]interface{}{
"allowedCollections": []interface{}{
map[string]interface{}{"collection": "web_outputs"},
},
},
})).SetTitle("Форматы страницы для вывода"),
"navTitle", field.String().SetTitle("Название для навигации"),
"navHide", field.Bool().SetTitle("Скрыть страницу из навигации").WithUI(&field.UI{Widget: "Checkbox"}),
"weight", field.Number(field.NumberFormatInt).SetTitle("Порядок следования").WithUI(&field.UI{Widget: "NumberInput"}),
).SetTitle("Настройки")
// Advanced
advanced := field.Object(
"scripts", field.Array(
field.Object(
"src", field.String().SetTitle("URL для загрузки скрипта"),
"type", field.String().SetTitle("Media Type скрипта"),
"content", field.String().SetTitle("Содержимое скрипта"),
),
).SetTitle("Дополнительные скрипты"),
).SetTitle("Расширенные настройки")
// Design
design := field.Object().WithIncludes("web_design").SetTitle("Дизайн")
//Variables
variables := field.Object(true, "variables", field.Array(getVarsField())).SetTitle("Переменные")
// Page
page := schema.New(
"content", content,
"seo", seo,
"settings", settings,
"advanced", advanced,
"design", design,
"variables", variables,
).WithMetadata(extension.MetadataKey, "perxisweb")
// Includes
page.SetIncludes(
field.Include{Ref: "hoop_item_options", Optional: true},
field.Include{Ref: "ext_web_pages_*", Optional: true},
)
//UI
page.Field.UI.ListView = &field.View{Options: map[string]interface{}{
"fields": []string{"path", "name", "updated_at", "updated_by", "state"},
"sort": []string{"path"},
"page_size": 50,
}}
page.Field.UI.Options["title"] = "name"
page.Field.UI.Options["key"] = "path"
page.Field.UI.Options["description"] = "path"
page.Field.UI.Widget = "Tabs"
page.Field.UI.Options["collection_icon"] = "ApartmentOutlined/FileTextOutlined"
_ = page.ConvertTypes()
return page
}
func getVarsSchema() (sch *schema.Schema) {
sch = schema.New(
"id", field.String(validate.Required()).SetTextSearch(true).SetTitle("Идентификатор переменной"),
"name", field.String().SetTextSearch(true).SetTitle("Название переменной"),
"value", field.String().SetTitle("Значение переменной"),
).WithMetadata(extension.MetadataKey, "perxisweb")
//UI
sch.Field.UI.ListView = &field.View{Options: map[string]interface{}{
"fields": []string{"id", "name", "updated_at", "updated_by", "state"},
"page_size": 50,
}}
sch.Field.UI.Options["collection_icon"] = "SettingOutlined/FileExcelOutlined"
return sch
}
func getVarsField() *field.Field {
return &getVarsSchema().Field
}
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"testing" "testing"
"time" "time"
...@@ -292,6 +293,82 @@ func TestSchemaUI_UnmarshalJSON(t *testing.T) { ...@@ -292,6 +293,82 @@ func TestSchemaUI_UnmarshalJSON(t *testing.T) {
assert.Equal(t, sch, schm) assert.Equal(t, sch, schm)
} }
func TestSchemaUI_UnmarshalYAML(t *testing.T) {
vw := &field.View{
Widget: "Widget",
Options: map[string]interface{}{"title": "name", "key": "name"},
}
ui := &field.UI{
Widget: "Widget",
Placeholder: "Placeholder",
Options: map[string]interface{}{"title": "name", "key": "name"},
ListView: vw,
ReadView: vw,
EditView: vw,
}
schm := schema.New(
"name", field.String().WithUI(ui),
)
schm.UI = ui
j := `
---
ui:
widget: Widget
placeholder: Placeholder
options:
title: name
key: name
read_view:
widget: Widget
options:
title: name
key: name
edit_view:
widget: Widget
options:
title: name
key: name
list_view:
widget: Widget
options:
title: name
key: name
type: object
params:
inline: false
fields:
name:
ui:
widget: Widget
placeholder: Placeholder
options:
title: name
key: name
read_view:
widget: Widget
options:
title: name
key: name
edit_view:
widget: Widget
options:
title: name
key: name
list_view:
widget: Widget
options:
title: name
key: name
type: string
params: {}
loaded: false
`
sch, err := schema.FromYAML(strings.NewReader(j))
require.NoError(t, err)
assert.Equal(t, sch, schm)
}
func TestSchema_GetField(t *testing.T) { func TestSchema_GetField(t *testing.T) {
sch := schema.New( sch := schema.New(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment