Problem/Motivation

We started to iterate over the primary widget and improved it a lot.
Now we start to experiment with more radical changes and we want to be careful.
Still lots of these steps are needed to transform the content editing experience into the next level.

Proposed resolution

Let's create a second widget and declare it experimental.

We still can transform the parent widget into better modularity, so subclass overrides are more simple.
The parent / original will stay the classic default widget - at least for a while.
The new experimental one will allow us to do progressive changes without the need that everyone agrees.
People can easily switch the widget and give the new experience a try.
We will then decide if we keep both in the long run, if we migrate some features back, or if we deprecate the classic one in the long run.
Nothing is decided yet except the fact that we need both for now.

Some changes, expecially the "Remove" => "X" transformation will be reverted.
We can also already discuss if we should revert the "Removed" / "Confirm removal" state too. But IMHO we should focus on the client side delete confirmation.

Remaining tasks

We should identify other isolated changes that we want to revert.
Implement. :-)

User interface changes

A second experimental widget. Needs a name...

API changes

CommentFileSizeAuthor
#40 Screenshot from 2017-01-11 08-31-30.png12.61 KBjohnchque
#40 interdiff-2837855-39-40.txt1.61 KBjohnchque
#40 offer_a_second-2837855-40.patch47.93 KBjohnchque
#39 offer_a_second-2837855-39.patch46.64 KBjohnchque
#39 interdiff-2837855-38-39.txt40.05 KBjohnchque
#38 interdiff-2837855-36-38.txt5.28 KBjohnchque
#38 offer_a_second-2837855-38.patch106.88 KBjohnchque
#36 interdiff-2837855-29-36.txt2.32 KBjohnchque
#36 offer_a_second-2837855-36.patch105.27 KBjohnchque
#29 interdiff-2837855-27-29.txt9.03 KBjohnchque
#29 offer_a_second-2837855-29.patch105.38 KBjohnchque
#27 interdiff-2837855-26-27.txt58.58 KBjohnchque
#27 offer_a_second-2837855-27.patch105.52 KBjohnchque
#26 interdiff-2837855-25-26.txt29.95 KBjohnchque
#26 offer_a_second-2837855-26.patch46.08 KBjohnchque
#25 interdiff-2837855-22-25.txt16.3 KBjohnchque
#25 offer_a_second-2837855-25.patch17.43 KBjohnchque
#22 interdiff-2837855-21-22.txt3.79 KBjohnchque
#22 offer_a_second-2837855-22.patch22.25 KBjohnchque
#21 interdiff-2837855-20-21.txt5.59 KBjohnchque
#21 offer_a_second-2837855-21.patch22.34 KBjohnchque
#20 interdiff-2837855-18-20.txt561 bytesjohnchque
#20 offer_a_second-2837855-20.patch16.8 KBjohnchque
#18 interdiff-2837855-14-18.txt2.86 KBjohnchque
#18 offer_a_second-2837855-18.patch16.16 KBjohnchque
#14 interdiff-2837855-10-14.txt11.05 KBjohnchque
#14 offer_a_second-2837855-14.patch15.91 KBjohnchque
#10 offer_a_second-2837855-10.patch10.51 KBjohnchque
#10 interdiff-2837855-8-10.txt2.43 KBjohnchque
#8 interdiff-2837855-6-8.txt10.92 KBjohnchque
#8 offer_a_second-2837855-8.patch10.49 KBjohnchque
#6 interdiff-2837855-4-6.txt679 bytesjohnchque
#6 offer_a_second-2837855-6.patch5.53 KBjohnchque
#4 offer_a_second-2837855-4.patch4.76 KBjohnchque
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

miro_dietiker created an issue. See original summary.

Berdir’s picture

One thing to consider is test coverage. How are we going to ensure that both the old and the new widget have sufficient test coverage?

How exactly are we going to define the widget as experimental? put it in th label? Make it part of an optional module?

johnchque’s picture

Assigned: Unassigned » johnchque

Gonna work on this.

johnchque’s picture

First try, not sure about naming yet. Css cloned.

Status: Needs review » Needs work

The last submitted patch, 4: offer_a_second-2837855-4.patch, failed testing.

johnchque’s picture

Tests fixed.

miro_dietiker’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests
  1. +++ b/src/Plugin/Field/FieldWidget/ExperimentalParagraphsWidget.php
    @@ -0,0 +1,32 @@
    + *   id = "entity_reference_revisions_paragraphs",
    ...
    + *   description = @Translation("An paragraphs form widget."),
    ...
    +class ExperimentalParagraphsWidget extends InlineParagraphsWidget {
    

    We need a name that is is made to stay. At one day it is no more experimental. And a description about its purpose.

  2. +++ b/src/Plugin/Field/FieldWidget/ExperimentalParagraphsWidget.php
    @@ -0,0 +1,32 @@
    + *   label = @Translation("Paragraphs"),
    

    We said the label MUST contain EXPERIMENTAL to create awareness.

What is missing:
- The revert of the "Remove" => "X" change - and make it an override of the widget.
- Test coverage.
We will need tests.

johnchque’s picture

Not sure if that is the best way to do it, neither if it is the right approach but certainly, it works. :)

Status: Needs review » Needs work

The last submitted patch, 8: offer_a_second-2837855-8.patch, failed testing.

johnchque’s picture

Fixing tests.

HongPong’s picture

I would change "paragraphs.experimental.css" to "paragraphs.alternative.css" or similar, it would make sense to label it as experimental in the body and labels but it would be best to not use in the file names. Thank you for developing this.

miro_dietiker’s picture

Status: Needs review » Needs work
+++ b/paragraphs.libraries.yml
@@ -10,4 +10,9 @@ drupal.paragraphs.admin:
+drupal.paragraphs.experimental:

+++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
@@ -0,0 +1,70 @@
+ *   id = "paragraphs",
+ *   label = @Translation("Experimental Paragraphs"),
+ *   description = @Translation("A paragraphs form widget which allows to add multiple paragraphs to a field."),
...
+    $elements['#attached']['library'][] = 'paragraphs/drupal.paragraphs.experimental';

We still need to define a better naming and a clearer description. Will discuss tomorrow and make a proposal.

miro_dietiker’s picture

Component: User interface » Experimental Widget
johnchque’s picture

Discussed with @miro_dietiker this approach might be better. I was thinking, what if instead we create methods for all parts of the widget, like buildTop which it can call to buildTitle and buildActions. But it is just a thought.

Status: Needs review » Needs work

The last submitted patch, 14: offer_a_second-2837855-14.patch, failed testing.

miro_dietiker’s picture

Let's do it like this:

The original widget:

 *   id = "entity_reference_paragraphs",
 *   label = @Translation("Paragraphs Classic"),
 *   description = @Translation("An paragraphs inline form widget."),

The experimental widget

 *   id = "paragraphs",
 *   label = @Translation("Paragraphs EXPERIMENTAL"),
 *   description = @Translation("An experimental paragraphs inline form widget."),

But yeah, i'm giving up to find a better name. xyz.experimental.abc is fine. We will need to do config updates anyway once we switch things as in default or deprecate something.

miro_dietiker’s picture

Keep in mind that certain methods only work if we have the same structure in a new widget. It's also likely a widget drops certain elements or outsputs different structure (such as no table - thus no table head) or adds new concepts such as a template selection, a drag and drop mode with completely different output (that should suppress regular form build processing) and many things more from our fancy roadmap.

johnchque’s picture

Fixed tests and changed the widget id's. I really think we should provide a buildTop(). :)

Status: Needs review » Needs work

The last submitted patch, 18: offer_a_second-2837855-18.patch, failed testing.

johnchque’s picture

Adding schema. No idea why it passes with php 7. :)

johnchque’s picture

johnchque’s picture

Berdir’s picture

  1. +++ b/css/paragraphs.admin.css
    --- /dev/null
    +++ b/css/paragraphs.experimental.css
    

    one idea to avoid "experimental":

    make separate css files or even libraries,name them after what they're actualy for. So this would be paragraphs.delete.css or something like that.

  2. +++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
    @@ -0,0 +1,72 @@
    +/**
    + * Plugin implementation of the 'entity_reference_revisions paragraphs' widget.
    + *
    + * @FieldWidget(
    + *   id = "paragraphs",
    + *   label = @Translation("Paragraphs EXPERIMENTAL"),
    + *   description = @Translation("An experimental paragraphs inline form widget."),
    + *   field_types = {
    + *     "entity_reference_revisions"
    + *   }
    + * )
    + */
    +class ParagraphsWidget extends InlineParagraphsWidget {
    

    Hm.

    I was wondering if we should do it like this or if we should just copy the file completely.

    This saves duplicate code now, but everything we want to do here will be more complicated and it will likely result in changes and refactoring of the existing widget.

    I'm not sure if we rather just want to copy it 1:1. One problem that I see test coverage, how do we ensure to not break any existing functionality if we don't run all the test we have with both widgets?

miro_dietiker’s picture

Yeah definitively, we will need to test both or the whole separation idea is pretty artificial.

johnchque’s picture

Changed approach, copied file. Still need to work on tests. :)

johnchque’s picture

OK, duplicated most of the tests. Thinking about if we should create a new test environment as diff module has now. That would speed up the tests. :)

johnchque’s picture

Status: Needs review » Needs work

The last submitted patch, 27: offer_a_second-2837855-27.patch, failed testing.

johnchque’s picture

Some changes.

tduong’s picture

Status: Needs review » Needs work

Was requested to review this patch.

  1. +++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php
    @@ -14,32 +14,27 @@ use Drupal\Core\Form\FormStateInterface;
    + *   label = @Translation("Paragraphs EXPERIMENTAL"),
    

    Is this intended to be capitalised (or was it for a personal test) ?

  2. +++ b/src/Tests/ParagraphsExperimentalAccessTest.php
    @@ -0,0 +1,146 @@
    +    $admin_user = [
    ...
    +    $this->loginAsAdmin($admin_user);
    ...
    +    $this->loginAsAdmin($admin_user);
    

    This actually is $admin_permissions. You could either give the permissions array as anonymous varable, save $admin_user = $this->loginAsAdmin(...) and then use a normal drupalLogin() below, or just rename this variable :P

  3. +++ b/src/Tests/ParagraphsExperimentalAccessTest.php
    @@ -0,0 +1,146 @@
    +    $edit = array(
    +      'settings[uri_scheme]' => 'private',
    +    );
    ...
    +      ->setComponent('field_paragraphs_demo', [
    +        'type' => 'paragraphs',
    +      ]);
    

    If there is just one item and it fits in the 80-char-length rule, I guess you can keep it in one single line.

  4. +++ b/src/Tests/ParagraphsExperimentalAddModesTest.php
    @@ -0,0 +1,244 @@
    +    $this->loginAsAdmin();
    +    $this->addParagraphedContentType('paragraphed_test', 'paragraphs_field');
    

    As these two lines, or at least addParagraphedContentType(), (are)/is executed first for each test method, you could add it to setUp() in this class (for every test class).

  5. +++ b/src/Tests/ParagraphsExperimentalAddModesTest.php
    @@ -0,0 +1,244 @@
    +    // Use the experimental widget.
    +    $form_display = EntityFormDisplay::load('node.paragraphed_test.default')
    +      ->setComponent('paragraphs_field', [
    +        'type' => 'paragraphs',
    +      ]);
    +    $form_display->save();
    

    This code is done multiple times in the new tests added.
    There is a helper method similar already, setAddMode($content_type, $paragraphs_field, $mode, $experimental = FALSE), that sets 'settings' => ['add_mode' => $mode] also.
    Should we have a more general method with:
    - $id to be loaded ($id seen so far: node.paragraphed_test.default, node.paragraphed_content_demo.default, node.article.default, contact_message.test_contact_form.default)
    - and the component $name to be set ($name seen so far: paragraphs, paragraphs_field, field_paragraphs, field_paragraphs_demo, paragraph.nested_paragraph.default),
    something like setParagraphsFormDisplayComponent($id, $name) in ParagraphsTestBase to reduce more lines everywhere?

  6. +++ b/src/Tests/ParagraphsExperimentalAddModesTest.php
    @@ -0,0 +1,244 @@
    +  protected function assertAddButtons($options) {
    ...
    +  protected function assertSelectOptions($options, $paragraphs_field) {
    
    +++ b/src/Tests/ParagraphsExperimentalInlineEntityFormTest.php
    @@ -0,0 +1,174 @@
    +  protected function setParagraphsWidgetMode($content_type, $paragraphs_field, $mode) {
    

    I've seen many asserts (and other kind of helper methods) duplicated (not only because of the cloned experimental X test). Maybe we could move all these helper methods in ParagraphsTestBase ?

  7. +++ b/src/Tests/ParagraphsExperimentalTranslationTest.php
    @@ -0,0 +1,856 @@
    +  protected $admin_user;
    

    Unused property.

Not entirely sure if your goal is to then keep these duplicated tests and later extend them for more experimental widget functionality, or it is just a temporary situation and they will be merged in the existing tests. But if these tests will be kept, couldn't we provide helper methods in something like a trait, or better rename the existing testBla to doTestBla and add testBla() { $this->doTestBla(); } and

testBlaExperimental() { // set something for experimental related context
$this->doTestBla(); }

since most of the new tests are adding something at the beginning of each test ?

Ok no, you are going to extend these tests so trying to avoid duplication won't work for later purpose...

miro_dietiker’s picture

We clone, because we want to divert without thinking:
What the old widget does should remain. What the new widget does should never affect the old one.
Thus cloning with initially having everything duplicated is the solution.

Berdir’s picture

+++ b/src/Tests/ParagraphsExperimentalContactTest.php
@@ -0,0 +1,55 @@
+
+namespace Drupal\paragraphs\Tests;
+

What about using a namespace for the tests?

Drupal\paragraphs\Tests\Classic
Drupal\paragraphs\Tests\Experimental

not sure about keeping the experimental in the class name too, but this should be easier to for example do a search and replace only in the new tests?

tduong’s picture

this should be easier to for example do a search and replace only in the new tests?

I was thinking about it as well.

Ok for duplicated code. But I still think we could provide helper methods to "encapsulate" pieces of tests, like

$this->addParagraphsType('text');
$this->fieldUIAddNewField(...);
$this->assertText(...);
file_put_contents(...);
$image = $this->container->get('file_system')->realpath(...);
$edit = [ ... ];
$this->drupalPostForm(NULL, $edit, 'Update');

...

Since, even without duplicating the tests, some processes are repeated in different tests. Sure it is not related to this issue though.

About naming this experimental widget, if I got it right what differs this from the classic one is that some "labeled buttons" are "reduced" to an icon or some processes (confirm remove, ...) are "reduced", all made for "imagery – guide visual treatments" or similar.
What about something like "Printed-based Design"/"Print Design" or if it is too confusing for an end-user maybe something like "Graphic Print Design"/"Graphic Design" (not sure about this one though)... Got inspired from material design principles explanations.

Berdir’s picture

That's the difference now, we want to make a lot more changes, so I think just sticking with Experimental is fine for now.

What about behaviors? Should we remove that from the classic widget completely as well? Will not be able to provide anything that's even remotely useful for users. Might need a notice then in the paragraph type settings that behaviors are only support with the experimental widget?

Berdir’s picture

Same with helper functions, we could add them for non-widget related things but that's something that can be done later.

johnchque’s picture

Addressed changes from previous comments. Open issues for helper methods and behavior plugins.

Status: Needs review » Needs work

The last submitted patch, 36: offer_a_second-2837855-36.patch, failed testing.

johnchque’s picture

Adding per-paragraph-type config tests. :)

johnchque’s picture

OK, so behavior plugins were removed from the classic plugin, tests were split. :)

johnchque’s picture

Added warning message for using behavior plugins with the experimental widget.

miro_dietiker’s picture

Status: Needs review » Fixed

Committed with resolving a conflict (removal in widget).

Tiny nitpick fixes (codestyle, missing .), committed. Now many patches need a reroll.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

groovedork’s picture

Could the front page of this module perhaps explain the difference between 'classic' and 'experimental'? It's a bit mysterious now.

miro_dietiker’s picture

@groovedork Best is you create a new separate issue for this task.