Make views consistent in code, schema, tests, and APIs

Created on 14 January 2022, about 3 years ago
Updated 7 February 2023, about 2 years ago

Problem/Motivation

Views is not consistent between its code, schema, and APIs. Tests confirm what it does, not what it should do.

This issue was created to elaborate on the bug and its solution. Patches were applied to the related issue 2976616 which narrowly focuses on what turns out to be a much bigger problem. Rather than rewrite its issue summary, this issue was added.

Note: Filtering the drupal project issues to search for 'schema' in bug reports on views.module in version 9.* reveals a few issues with problems in the same namespace but of a much narrower scope. Most have been idle for 4+ years. To my knowledge, none proposes what is contained herein other than issue 2836390 proposes to change the stored plugin_id in config.

Steps to reproduce

This bug surfaced while testing aggregation on a view with fields. The config of such a view always fails schema checks because views code:

  • internally (think runtime) changes the plugin_id from 'field' to 'numeric' when aggregation type is count, min, max, etc. (something other than group)
  • adds the config items from the 'numeric' field handler (e.g. precision, decimal)
  • retains the config items from the 'field' field handler (e.g. click_sort_column)
  • does not change the stored plugin_id in config

and the schema.yml says:

  • 'numeric' (views.field.numeric) and 'field' (views.field.field) each extend 'views_field' data type;

so the config items added by 'field' are not included in 'numeric' and vice versa; hence the schema conflicts.

From the views UI, the config changes only occur if the field handler is edited and the view saved. For example, turn on aggregation for the view and save the view. The config items on the fields in the view are not changed (they continue to use 'group together' for aggregation). Edit one of them and save the view. Now the field config includes the items from the 'numeric' field (and the default 'field') handler.

The straightforward solution (used in patches on issue 2976616) was to:

  • change the stored plugin_id in config
  • store only the config items applicable to the handler
  • add plugin_id to the HandlerBase::defineOptions() method

This fixed the immediate problem but exposed a host of config, code, and test issues in views.

First, to store only the config items applicable to the handler meant changing the last parameter in the function call to unpackOptions() in PluginBase::init().

-    $this->unpackOptions($this->options, $options);
+    $this->unpackOptions($this->options, $options, $definition, FALSE);

The last parameter (the undocumented $all parameter) is what allows views to accept and retain any options in the handler config (e.g. from import) and never prune those options that do not apply to the handler. For example, one could make up options and add them to a view export, then import the view, and views would retain the bogus items in persistent config (database and export). All because of having $all = TRUE. Changing $all to FALSE prunes the unwanted config items after the handler is edited and the view is saved.

But this change to $all also exposes a lot of errors in code and tests (both assertions and the views being tested).

PluginBase::init() calls $this->defineOptions() to gather the options applicable to the handler. These methods in EntityField (the class behind the 'field' handler), FieldPluginBase, HandlerBase, and PluginBase essentially duplicate the contents of the schema.yml files. Well, they are supposed to duplicate if there are to be no schema conflicts, but they do not and there are. Examples are:

views/argument/ManyToOne conditionally defines 'break_phrase' yet it should always be present.

views/field/FieldPluginBase

  • recognizes in code extra keys in the 'alter' options array like link_attributes, query, and fragment
  • these keys are not declared in defineOptions() nor in schema.yml files
  • the tests create handlers with these keys
  • views does not retain the keys because of the $all change, so tests fail
  • to fix the tests means adding the keys to defineOptions()
  • but the schema is not in sync with views code, so tests fail
  • to fix the tests means adding the keys to schema.yml
  • but the test views omit these keys, so tests fail
  • to fix the tests means adding the keys to some of the test views

Interestingly, the addition of 'plugin_id' to HandlerBase::defineOptions() [NOT the reorder in 'views_handler' data type in views.data_types.schema.yml] led to some major test failures. After a view is loaded and the displays are initialized, the plugin_id key is now first in line in the options array of each handler. Some of the tests save the view after initialization and compare it to the original. The tests utilize some rather robust code that checks array identity (the '===' operator) and fails (if I understand this correctly) because the order of keys differ in the input and output. (Note: switching the order in the respective views.yml files made the tests pass.) The output 'plugin_id' key is first on each handler item as opposed to some other position. The 'plugin_id' key had been declared in 5 of the 6 handlers: views_field, views_filter, views_sort, views_area, and views_argument; but not in views_relationship. It is also defined in their common parent, views_handler. This in itself is bad form. To 'fix' the tests necessitated moving the plugin_id key to first place in each view used in tests (which includes all the default views declared in a module config/optional directory).

Several of the test views have incorrect values (yet the tests would pass):

views.view.test_field_get_entity.yml has incorrect values for:

  • table: users
  • plugin_id: user

which should be:

  • table: users_field_data
  • plugin_id: field

views.view.test_display_defaults.yml omits plugin_id: standard

views.view.test_{un,}group_rows.yml includes a field that does not exist

See the patches on the aforementioned issue if you are curious.

Proposed resolution

Described in detail above. To recap:

  • config should reflect the plugin_id views uses internally
  • config should only include options applicable to the plugin_id
  • code should include all options in defineOptions() methods
  • schema should include all options

Remaining tasks

None. Tests are updated in the patch on issue 2976616.

User interface changes

None.

API changes

None.

Data model changes

None. (Unless you consider bug fixes a model change.)

Release notes snippet

Not applicable.

🌱 Plan
Status

Needs review

Version

10.1 ✨

Component
Views  β†’

Last updated about 3 hours ago

Created by

πŸ‡ΊπŸ‡ΈUnited States solotandem

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024