Customization
Fieldsets and i18n
Fieldsets give you the opportunity to gather a set of defined attributes in a single field of a type
Please read about schema extensions first.
The fieldset directive and type
The Fieldset
type and the @fieldset
directive give you the possibility to define a field on a type that gathers a certain set of fields of that same type. This gives you the opportunity to collect i.e. your users custom attributes in one set. This comes in especially handy for client applications, that do not know about the custom fields of a user. With the help of fieldsets they just need to know a single field of the user and can render mutliple attributes with the correct types and labels.
For example for a User
type with the following fields, you can retrieve the selected attributes of the user in a fieldset (see fieldset1
and fieldset2
). This works for all fields that are of a graphql scalar type, or types that implement Labeled
or Node
interface (like User
, Group
, Container
, etc.) as well as for other fieldsets (see combinedFieldset
):
extend type User {
booleanScalar: Boolean @ldapField(attribute: "passwordAllowChange")
stringScalar: String @ldapField(attribute: "givenName")
intScalar: Int @ldapField(attribute: "roomNumber")
dateScalar: String @ldapField(attribute: "loginTime") @ldapTimestamp @formatDateTime(format: "yyyy-MM-dd'T'hh:mm:ss xxx")
resolvedUser: User @ldapField(attribute: "directReports") @resolveDN
resolvedUsers: [User!]! @ldapField(attribute: "directReports") @resolveDN
resolvedGroup: Group @ldapField(attribute: "groupMembership") @resolveDN
resolvedGroups: [Group!]! @ldapField(attribute: "groupMembership") @resolveDN
fieldset1: Fieldset @fieldset(fields: ["stringScalar", "booleanScalar", "intScalar", "dateScalar"])
fieldset2: Fieldset @fieldset(fields: ["resolvedUser", "resolvedUsers", "resolvedGroup"])
combinedFieldset: Fieldset @fieldset(fields: ["testpanel1", "testpanel2"])
}
Below you see an example query with all possible fields requested for a fieldset and the corresponding response:
Query:
query GetUserFieldsets {
userByDN(dn: "cn=example,o=data") {
fieldset1 {
label
description
fields {
name
description
label
type
value
}
}
}
}
Response:
{
data: {
userByDN: {
fieldset1: {
label: "A configured label translation for fieldset1",
description: "A configured description translation for fieldset1"
fields: [
{
name: "stringScalar",
label: "A configured label translation for stringScalar",
description: "A configured description translation for stringScalar"
type: "String",
value: "Max"
},
{
"name": "booleanScalar",
label: "A configured label translation for booleanScalar",
description: null,
type: "Boolean",
value: true
},
{
"name": "intScalar",
label: "A configured label translation for intScalar",
description: null,
type: "Int",
value: 103
},
{
"name": "dateScalar",
label: "A configured label translation for dateScalar",
description: "A configured description translation for dateScalar"
type: "String",
value: "2022-04-26T12:20:11 +00:00"
}
]
}
}
}
}
I18n
The IdentityHub has build in support for i18n. That means you can provide translation files for different languages and namespaces. There are 3 configuration properties that influence the translation mechanism:
[i18n]
# the path to the locales directory relative to the config directory
# defaults to './locales' and will be created if not existant
localesDirectory="./locales"
# the pattern for how to look up translation keys
# defaults to "{{lng}}/{{ns}}.yaml".
# For the default inside the locales directory there should be a folder for every supported language (i.e. "de" or "de-CH") and then a yaml file for every supported namespace
filePattern="{{lng}}/{{ns}}.yaml"
# language to use if translations in user language are not available
# defaults to "en"
fallbackLanguage="en"
Currently we support two namespaces. The common
namespace and the fieldset
namespace. If a translation for a certain key is requested for certain languages in a namespace and none of the languages are available for the requested namespace and key, we will fallback to the common
namespace. If no translation could be found at all, we return the requested key.
Currently we support yaml
, yml
, json
, and js
for the translation files. At the moment all translations files must use the same file format (i.e. extension).
Localized fieldsets
For Fieldsets the fieldset
namespace of our i18n library comes to use. With the translation files you can provide the labels of a Fieldset
and the corresponding Field
s. These labels are determined using the name of the type that contains the fieldset and the name of the fieldset itself. If no language(s) are passed for the fieldset in the Query, we use the languages from the Accept-Language
header of the request. The fallbackLanguage
described above is always added to the languages, if not already present.
Considering the example above, with the addition of passing the preferred languages:
query GetUserFieldsets {
userByDN(dn: "cn=example,o=data") {
fieldset1(locale: ["de", "fr", "es"]) {
label
fields {
name
label
type
value
}
}
}
}
The possible translation keys for the label
of the fieldset would be
<type>.<fieldset>.label
(User.fieldset1.label
)<fieldset>.label
(fieldset1.label
)<type>.<fieldset>
(User.fieldset1
)<fieldset>
(fieldset1
)- If no translation is found, the fieldset name is used.
and for the description
:
<type>.<fieldset>.description
—User.fieldset1.description
<fieldset>.description
—fieldset1.description
- If no translation is found,
null
is used
within the translation namespace fieldset
, after that in the common
namespace.
For the field translations, we use the same logic as above, with the following possible keys for the label
of a field:
<type>.<fieldset>.<field>.label
—User.fieldset1.resolvedUser.label
<type>.<field>.label
—User.resolvedUser.label
<fieldset>.<field>.label
—fieldset1.resolvedUser.label
<field>.label
—resolvedUser.label
<type>.<fieldset>.<field>
—User.fieldset1.resolvedUser
<type>.<field>
—User.resolvedUser
<fieldset>.<field>
—fieldset1.resolvedUser
<field>
—resolvedUser
- If no translation is found, the field name is used.
and for the description
:
<type>.<fieldset>.<field>.description
—User.fieldset1.resolvedUser.description
<type>.<field>.description
—User.resolvedUser.description
<fieldset>.<field>.description
—fieldset1.resolvedUser.description
<field>.description
—resolvedUser.description
- If no translation is found,
null
is used