Docs: Render map type fields in generated docs (#62022)

* Render map type in docs

* Generate

* Clenaup rendering properties

* Minor updates

* Update maps rendering
This commit is contained in:
Tania 2023-02-02 13:02:29 +01:00 committed by GitHub
parent 517e614661
commit 3c01ae2c9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 149 additions and 97 deletions

View File

@ -13,26 +13,19 @@ title: ServiceAccount kind
## Properties
| Property | Type | Required | Description |
|-----------------|--------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------|
| `avatarUrl` | string | **Yes** | AvatarUrl is the service account's avatar URL. It allows the frontend to display a picture in front<br/>of the service account. |
| `id` | integer | **Yes** | ID is the unique identifier of the service account in the database. |
| `isDisabled` | boolean | **Yes** | IsDisabled indicates if the service account is disabled. |
| `login` | string | **Yes** | Login of the service account. |
| `name` | string | **Yes** | Name of the service account. |
| `orgId` | integer | **Yes** | OrgId is the ID of an organisation the service account belongs to. |
| `role` | string | **Yes** | OrgRole is a Grafana Organization Role which can be 'Viewer', 'Editor', 'Admin'. Possible values are: `Admin`, `Editor`, `Viewer`. |
| `tokens` | integer | **Yes** | Tokens is the number of active tokens for the service account.<br/>Tokens are used to authenticate the service account against Grafana. |
| `accessControl` | [object](#accesscontrol) | No | AccessControl metadata associated with a given resource. |
| `created` | integer | No | Created indicates when the service account was created. |
| `teams` | string[] | No | Teams is a list of teams the service account belongs to. |
| `updated` | integer | No | Updated indicates when the service account was updated. |
## accessControl
AccessControl metadata associated with a given resource.
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| Property | Type | Required | Description |
|-----------------|--------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------|
| `avatarUrl` | string | **Yes** | AvatarUrl is the service account's avatar URL. It allows the frontend to display a picture in front<br/>of the service account. |
| `id` | integer | **Yes** | ID is the unique identifier of the service account in the database. |
| `isDisabled` | boolean | **Yes** | IsDisabled indicates if the service account is disabled. |
| `login` | string | **Yes** | Login of the service account. |
| `name` | string | **Yes** | Name of the service account. |
| `orgId` | integer | **Yes** | OrgId is the ID of an organisation the service account belongs to. |
| `role` | string | **Yes** | OrgRole is a Grafana Organization Role which can be 'Viewer', 'Editor', 'Admin'. Possible values are: `Admin`, `Editor`, `Viewer`. |
| `tokens` | integer | **Yes** | Tokens is the number of active tokens for the service account.<br/>Tokens are used to authenticate the service account against Grafana. |
| `accessControl` | map[string]boolean | No | AccessControl metadata associated with a given resource. |
| `created` | integer | No | Created indicates when the service account was created. |
| `teams` | string[] | No | Teams is a list of teams the service account belongs to. |
| `updated` | integer | No | Updated indicates when the service account was updated. |

View File

@ -13,23 +13,16 @@ title: Team kind
## Properties
| Property | Type | Required | Description |
|-----------------|--------------------------|----------|----------------------------------------------------------|
| `created` | integer | **Yes** | Created indicates when the team was created. |
| `memberCount` | integer | **Yes** | MemberCount is the number of the team members. |
| `name` | string | **Yes** | Name of the team. |
| `orgId` | integer | **Yes** | OrgId is the ID of an organisation the team belongs to. |
| `permission` | integer | **Yes** | Possible values are: `0`, `1`, `2`, `4`. |
| `updated` | integer | **Yes** | Updated indicates when the team was updated. |
| `accessControl` | [object](#accesscontrol) | No | AccessControl metadata associated with a given resource. |
| `avatarUrl` | string | No | AvatarUrl is the team's avatar URL. |
| `email` | string | No | Email of the team. |
## accessControl
AccessControl metadata associated with a given resource.
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| Property | Type | Required | Description |
|-----------------|--------------------|----------|----------------------------------------------------------|
| `created` | integer | **Yes** | Created indicates when the team was created. |
| `memberCount` | integer | **Yes** | MemberCount is the number of the team members. |
| `name` | string | **Yes** | Name of the team. |
| `orgId` | integer | **Yes** | OrgId is the ID of an organisation the team belongs to. |
| `permission` | integer | **Yes** | Possible values are: `0`, `1`, `2`, `4`. |
| `updated` | integer | **Yes** | Updated indicates when the team was updated. |
| `accessControl` | map[string]boolean | No | AccessControl metadata associated with a given resource. |
| `avatarUrl` | string | No | AvatarUrl is the team's avatar URL. |
| `email` | string | No | Email of the team. |

View File

@ -108,18 +108,44 @@ type templateData struct {
// Copied from https://github.com/marcusolsson/json-schema-docs and slightly changed to fit the DocsJenny
type schema struct {
ID string `json:"$id,omitempty"`
Ref string `json:"$ref,omitempty"`
Schema string `json:"$schema,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Required []string `json:"required,omitempty"`
Type PropertyTypes `json:"type,omitempty"`
Properties map[string]*schema `json:"properties,omitempty"`
Items *schema `json:"items,omitempty"`
Definitions map[string]*schema `json:"definitions,omitempty"`
Enum []Any `json:"enum"`
Default any `json:"default"`
ID string `json:"$id,omitempty"`
Ref string `json:"$ref,omitempty"`
Schema string `json:"$schema,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Required []string `json:"required,omitempty"`
Type PropertyTypes `json:"type,omitempty"`
Properties map[string]*schema `json:"properties,omitempty"`
Items *schema `json:"items,omitempty"`
Definitions map[string]*schema `json:"definitions,omitempty"`
Enum []Any `json:"enum"`
AdditionalProperties *schema `json:"additionalProperties"`
Default any `json:"default"`
}
func renderMapType(props *schema) string {
if props == nil {
return ""
}
if props.Type.HasType(PropertyTypeObject) {
name, anchor := propNameAndAnchor(props.Title, props.Title)
return fmt.Sprintf("[%s](#%s)", name, anchor)
}
if props.AdditionalProperties != nil {
return "map[string]" + renderMapType(props.AdditionalProperties)
}
if props.Items != nil {
return "[]" + renderMapType(props.Items)
}
var types []string
for _, t := range props.Type {
types = append(types, string(t))
}
return strings.Join(types, ", ")
}
func jsonToMarkdown(jsonData []byte, tpl string, kindName string) ([]byte, error) {
@ -189,6 +215,21 @@ func resolveSchema(schem *schema, root *simplejson.Json) (*schema, error) {
*schem.Items = *foo
}
if schem.AdditionalProperties != nil {
if schem.AdditionalProperties.Ref != "" {
tmp, err := resolveReference(schem.AdditionalProperties.Ref, root)
if err != nil {
return nil, err
}
*schem.AdditionalProperties = *tmp
}
foo, err := resolveSchema(schem.AdditionalProperties, root)
if err != nil {
return nil, err
}
*schem.AdditionalProperties = *foo
}
return schem, nil
}
@ -242,7 +283,7 @@ func (s schema) Markdown(level int) string {
var buf bytes.Buffer
if s.Title != "" {
if s.Title != "" && s.AdditionalProperties == nil {
fmt.Fprintln(&buf, makeHeading(s.Title, level))
fmt.Fprintln(&buf)
}
@ -289,9 +330,9 @@ func findDefinitions(s *schema) []*schema {
// properties for them recursively.
var objs []*schema
for k, p := range s.Properties {
// Use the identifier as the title.
if p.Type.HasType(PropertyTypeObject) {
definition := func(k string, p *schema) {
if p.Type.HasType(PropertyTypeObject) && p.AdditionalProperties == nil {
// Use the identifier as the title.
if len(p.Title) == 0 {
p.Title = k
}
@ -312,6 +353,15 @@ func findDefinitions(s *schema) []*schema {
}
}
for k, p := range s.Properties {
// If a property has AdditionalProperties, then it's a map
if p.AdditionalProperties != nil {
definition(k, p.AdditionalProperties)
}
definition(k, p)
}
// Sort the object schemas.
sort.Slice(objs, func(i, j int) bool {
return objs[i].Title < objs[j].Title
@ -331,67 +381,36 @@ func printProperties(w io.Writer, s *schema) {
// Buffer all property rows so that we can sort them before printing them.
rows := make([][]string, 0, len(s.Properties))
for k, p := range s.Properties {
// Generate relative links for objects and arrays of objects.
propType := make([]string, 0, len(p.Type))
for _, pt := range p.Type {
switch pt {
case PropertyTypeObject:
name, anchor := propNameAndAnchor(k, p.Title)
propType = append(propType, fmt.Sprintf("[%s](#%s)", name, anchor))
case PropertyTypeArray:
if p.Items != nil {
for _, pi := range p.Items.Type {
if pi == PropertyTypeObject {
name, anchor := propNameAndAnchor(k, p.Items.Title)
propType = append(propType, fmt.Sprintf("[%s](#%s)[]", name, anchor))
} else {
propType = append(propType, fmt.Sprintf("%s[]", pi))
}
}
} else {
propType = append(propType, string(pt))
}
default:
propType = append(propType, string(pt))
}
}
var propTypeStr string
if len(propType) == 1 {
propTypeStr = propType[0]
} else if len(propType) == 2 {
propTypeStr = strings.Join(propType, " or ")
} else if len(propType) > 2 {
propTypeStr = fmt.Sprintf("%s, or %s", strings.Join(propType[:len(propType)-1], ", "), propType[len(propType)-1])
}
for key, val := range s.Properties {
typeStr := propTypeStr(key, val)
// Emphasize required properties.
var required string
if in(s.Required, k) {
if in(s.Required, key) {
required = "**Yes**"
} else {
required = "No"
}
desc := p.Description
desc := val.Description
if len(p.Enum) > 0 {
vals := make([]string, 0, len(p.Enum))
for _, e := range p.Enum {
if len(val.Enum) > 0 {
vals := make([]string, 0, len(val.Enum))
for _, e := range val.Enum {
vals = append(vals, e.String())
}
desc += " Possible values are: `" + strings.Join(vals, "`, `") + "`."
}
if p.Default != nil {
desc += fmt.Sprintf(" Default: `%v`.", p.Default)
if val.Default != nil {
desc += fmt.Sprintf(" Default: `%v`.", val.Default)
}
rows = append(rows, []string{fmt.Sprintf("`%s`", k), propTypeStr, required, formatForTable(desc)})
rows = append(rows, []string{fmt.Sprintf("`%s`", key), typeStr, required, formatForTable(desc)})
}
// Sort by the required column, then by the name column.
// Sort by the required column, then by the key column.
sort.Slice(rows, func(i, j int) bool {
if rows[i][2] < rows[j][2] {
return true
@ -406,6 +425,53 @@ func printProperties(w io.Writer, s *schema) {
table.Render()
}
func propTypeStr(propName string, propValue *schema) string {
// If the property has AdditionalProperties, it is most likely a map type
if propValue.AdditionalProperties != nil {
mapValue := renderMapType(propValue.AdditionalProperties)
return "map[string]" + mapValue
}
propType := make([]string, 0, len(propValue.Type))
// Generate relative links for objects and arrays of objects.
for _, pt := range propValue.Type {
switch pt {
case PropertyTypeObject:
name, anchor := propNameAndAnchor(propName, propValue.Title)
propType = append(propType, fmt.Sprintf("[%s](#%s)", name, anchor))
case PropertyTypeArray:
if propValue.Items != nil {
for _, pi := range propValue.Items.Type {
if pi == PropertyTypeObject {
name, anchor := propNameAndAnchor(propName, propValue.Items.Title)
propType = append(propType, fmt.Sprintf("[%s](#%s)[]", name, anchor))
} else {
propType = append(propType, fmt.Sprintf("%s[]", pi))
}
}
} else {
propType = append(propType, string(pt))
}
default:
propType = append(propType, string(pt))
}
}
if len(propType) == 0 {
return ""
}
if len(propType) == 1 {
return propType[0]
}
if len(propType) == 2 {
return strings.Join(propType, " or ")
}
return fmt.Sprintf("%s, or %s", strings.Join(propType[:len(propType)-1], ", "), propType[len(propType)-1])
}
func propNameAndAnchor(prop, title string) (string, string) {
if len(title) > 0 {
return title, strings.ToLower(title)