Introduce a field DSL for integrations
What does this MR do and why?
This MR tries to improve the developer experience by introducing a DSL for defining fields on integrations.
A field includes both the field descriptor metadata, and generates
prop_accessor
or data_field
accessors on the class.
It aims to make writing an integration class more declarative and less repetitive, but without removing access to the underlying methods if needed. Currently we have to keep the storage methods (property and data field accessors) in sync with the field names - this DSL enforces that, and would lay the basis for nice things, like being able to rename fields while keeping underlying storage the same.
This enables us to have the following DSL:
field :foo,
required: true
help: -> { _('Some translated help text') }
field :bar,
section: 'numbers',
type: 'number'
sensitive_field :token,
required: true,
Instead of the current:
prop_accessor :foo
prop_accessor :bar
prop_accessor :token
def fields
[
{
name: 'foo',
required: true,
type: 'text',
help: _('Some translated help text')
},
{
name: 'bar',
section: 'numbers',
type: 'number'
},
{
name: 'token',
required: true,
type: 'password',
}
]
end
POC is available in Integrations::Jira
.
Future work
Section DSL
We can do the same thing for sections, and aim for an API like:
section 'heading', description: 'some description' do
field :foo
field :bar
end
Where each field in the section has Field#section
set implicitly, and the
section is registered in much the same way the fields are.
For that we do need to be careful that sections can be re-opened for addition of fields later, which is useful for subclasses.
Field
Moving value access to Currently in the field serialization entity, we rely on a one-to-one mapping between field names and instance methods to access values. This makes field renaming more onerous, and it requires adding method aliases to keep things in sync, and it isn't clear why things are related.
If we move value resolution to the field class, then we can customize it:
field :renamed,
accessor: :old_name
This would set up a prop_accessor :old_name
and use that to access the value,
keeping the link between the two in one place.
Generating API configuration
Having this configuration available statically rather than purely on instances would enable us to centralize some of our other configuration and reduce the development burden for new integrations.
Hopefully, we could replace much of the manual configuration in
API::Helpers::IntegrationsHelpers
by generating it automatically given the
available integration names and the statically available field definitions,
thus unifying the UI configuration and the API configuration.
This could have a substantial code-size reduction and reduce the need to keep different layers of the application syncrhonized, leaving the models as the SSOT.
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.
See: #353822 (closed)