Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LPS-115484 As a developer I can declare theme's customizable CSS variables #17

Closed
wants to merge 7 commits into from

Conversation

wincent
Copy link

@wincent wincent commented Jun 16, 2020

This was sent to me at https://github.com/wincent/liferay-portal/pull/357 — tests failed there, so I am going to take that as an opportunity to relocate this here instead and re-run them. Note the dependency on a prior pull (https://github.com/wincent/liferay-portal/pull/354) — the last three commits are the new ones, based on top of the other pull.

cc @izaera

Original message follows.


This is the continuation of https://github.com/wincent/liferay-portal/pull/354.

This is a partial PR based on the original PoC https://github.com/jbalsas/liferay-portal/pull/2211. It adds a service to retrieve CSS variable declarations inside themes' WAR files. These declarations will then be used to create customization forms in future PRs. Then, those forms will save configuration values that will be retrieved by a ScopedCSSVariablesProvider to inject them in the rendered HTML.

There's nothing to test manually in this PR as it is not yet possible to do anything from the GUI.

@liferay-continuous-integration
Copy link
Collaborator

To conserve resources, the PR Tester does not automatically run for every pull.

If your code changes were already tested in another pull, reference that pull in this pull so the test results can be analyzed.

If your pull was never tested, comment "ci:test" to run the PR Tester for this pull.

@wincent
Copy link
Author

wincent commented Jun 16, 2020

ci:test:sf

@wincent
Copy link
Author

wincent commented Jun 16, 2020

ci:test

@liferay-continuous-integration
Copy link
Collaborator

✔️ ci:test:sf - 1 out of 1 jobs passed in 3 minutes

Click here for more details.

Base Branch:

Branch Name: master
Branch GIT ID: a5641e2633f4e37e754378e298e1a37f04888f3d

Sender Branch:

Branch Name: LPS-115484
Branch GIT ID: 573a9b32b9d010f06cc8410b8ecdc0a733ccc293

1 out of 1jobs PASSED
1 Successful Jobs:
For more details click here.

@liferay-continuous-integration
Copy link
Collaborator

❌ ci:test:default - 291 out of 316 jobs passed in 4 hours

Click here for more details.

Base Branch:

Branch Name: master
Branch GIT ID: a5641e2633f4e37e754378e298e1a37f04888f3d

Copied in Private Modules Branch:

Branch Name: master-private
Branch GIT ID: 1048e471849c8076c4823cc7dd4b82abffdac7c4

ci:test:default - 291 out of 316 jobs PASSED

25 Failed Jobs:

291 Successful Jobs:
For more details click here.

Failures unique to this pull:

  1. test-portal-acceptance-pullrequest-batch(master)/modules-unit-jdk8
    Job Results:

    5910 Tests Passed.
    2 Tests Failed.

    1. AXIS_VARIABLE=1,label_exp=!master #404696
      1. ScopedCSSVariablesTopHeadDynamicIncludeTest.testIncludeWithMultipleProviders
        org.junit.ComparisonFailure: expected:<...a-track="temporary" [id="liferayCssVariablesCSS" ]type="text/css">
        :ro...> but was:<...a-track="temporary" []type="text/css">
        :ro...>
        	at org.junit.Assert.assertEquals(Assert.java:115)
        	at org.junit.Assert.assertEquals(Assert.java:144)
        	at com.liferay.frontend.css.variables.web.internal.servlet.taglib.ScopedCSSVariablesTopHeadDynamicIncludeTest.testIncludeWithMultipleProviders(ScopedCSSVariablesTopHeadDynamicIncludeTest.java:197)
        	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        	at java.lang.reflect.Method.invoke(Method.java:498)
        	at org.junit.runners.model.FrameworkMethod\$1.runReflectiveCall(FrameworkMethod.java:50)
        	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
        	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
        	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
        	at org.junit.runners.ParentRunner\$3.run(ParentRunner.java:290)
        	at org.junit.runners.ParentRunner\$1.schedule(ParentRunner.java:71)
        	at org.juni...
      2. ScopedCSSVariablesTopHeadDynamicIncludeTest.testInclude
        org.junit.ComparisonFailure: expected:<...a-track="temporary" [id="liferayCssVariablesCSS" ]type="text/css">
        :ro...> but was:<...a-track="temporary" []type="text/css">
        :ro...>
        	at org.junit.Assert.assertEquals(Assert.java:115)
        	at org.junit.Assert.assertEquals(Assert.java:144)
        	at com.liferay.frontend.css.variables.web.internal.servlet.taglib.ScopedCSSVariablesTopHeadDynamicIncludeTest.testInclude(ScopedCSSVariablesTopHeadDynamicIncludeTest.java:94)
        	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        	at java.lang.reflect.Method.invoke(Method.java:498)
        	at org.junit.runners.model.FrameworkMethod\$1.runReflectiveCall(FrameworkMethod.java:50)
        	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
        	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
        	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
        	at org.junit.runners.ParentRunner\$3.run(ParentRunner.java:290)
        	at org.junit.runners.ParentRunner\$1.schedule(ParentRunner.java:71)
        	at org.junit.runners.ParentRunner...

Failures in common with acceptance upstream results at a5641e2:
  1. test-portal-acceptance-pullrequest-batch(master)/tck-jdk8
    Job Results:

    0 Tests Passed.
    1 Test Failed.

    1. AXIS_VARIABLE=0,label_exp=!master #397816
           [exec] [INFO] BUILD FAILURE
           [exec] [INFO] ------------------------------------------------------------------------
           [exec] [INFO] Total time:  07:19 min
           [exec] [INFO] Finished at: 2020-06-16T08:42:46-07:00
           [exec] [INFO] ------------------------------------------------------------------------
           [exec] [ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.17:test (default-test) on project tck-driver: There are test failures.
           [exec] [ERROR] 
           [exec] [ERROR] Please refer to /opt/dev/projects/github/portals-pluto/portlet-tck_3.0/driver/target/surefire-reports for the individual test results.
           [exec] [ERROR] -> [Help 1]
           [exec] [ERROR] 
           [exec] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
           [exec] [ERROR] Re-run Maven using the -X switch to enable full debug logging.
           [exec] [ERROR] 
           [exec] [ERROR] For more information about the errors and possible solutions, please read the following articles:
           [exec] [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
           [exec] [ERROR] 
           [exec] [ERROR] After correcting the problems, you can resume the build with the command
           [exec] [ERROR]   mvn <args> -rf :tck-driver
      [stopwatch] [run.batch.test.action: 23:40.684 sec]
           [echo] The following error occurred while executing this line:
           [echo] /opt/dev/projects/github/liferay-portal/build-test-batch.xml:361: The following error occurred while executing this line:
           [echo] /opt/dev/projects/github/liferay-portal/build-test-batch.xml:6867: The following error occurred while executing this line:
           [echo] /opt/dev/projects/github/liferay-portal/build-test-tck.xml:161: The following error occurred while executing this line:
           [echo] /opt/dev/projects/github/liferay-portal/build-common.xml:528: The following error occurred while executing this line:
           [echo] /opt/dev/projects/github/liferay-portal/build-common.xml:647: exec returned: 1
            [get] Getting: http://test-1-6/job/test-portal-acceptance-pullrequest-batch(master)/AXIS_VARIABLE=0,label_exp=!master/397816//consoleText
            [get] To: /opt/dev/projects/github/liferay-portal/20200616084246850.txt
         [delete] Deleting: /opt/dev/projects/github/liferay-portal/20200616084246850.txt
         [delete] Deleting: /opt/dev/projects/github/liferay-portal/null761003950.properties
  2. test-portal-acceptance-pullrequest-batch(master)/archived-modules-test-jdk8
    Job Results:

    144 Tests Passed.
    1 Test Failed.

    1. AXIS_VARIABLE=0,label_exp=!master #403426
      1. PortalLogAssertorTest.testScanXMLLog
        junit.framework.AssertionFailedError: 
        Failed session factory verification
        java.lang.IllegalStateException: Wrong current transaction manager, current session factory classes metadata: \{com.liferay.portal.model.impl.BrowserTrackerImpl=SingleTableEntityPersister(com.liferay.portal.model.impl.BrowserTrackerImpl), com.liferay.portal.model.impl.LayoutImpl=SingleTableEntityPersister(com.liferay.portal.model.impl.LayoutImpl), com.liferay.portal.model.impl.UserNotificationDeliveryImpl=SingleTableEntityPersister(com.liferay.portal.model.impl.UserNotificationDeliveryImpl), com.liferay.portlet.social.model.impl.SocialActivityCounterImpl=SingleTableEntityPersister(com.liferay.portlet.social.model.impl.SocialActivityCounterImpl), com.liferay.portal.model.impl.RepositoryEntryImpl=SingleTableEntityPersister(com.liferay.portal.model.impl.RepositoryEntryImpl), com.liferay.portal.model.impl.LayoutBranchImpl=SingleTableEntityPersister(com.liferay.portal.model.impl.LayoutBranchImpl), com.liferay.portal.model.impl.ListTypeImpl=SingleTableEntityPersister(com.liferay.portal.model.impl.ListTypeImpl), com.liferay.portal.model.impl.LayoutFriendlyURLImpl=SingleTableEntityPersister(com.liferay.portal.model.impl.LayoutFriendlyURLImpl), com.liferay.portal.model.impl.PortletImpl=SingleTableEntityPersister(com.liferay.portal.model.impl.PortletImpl), com.liferay.portal.model.impl.AddressImpl=SingleTableEntityPersister(com.liferay.portal.model.impl.AddressImpl), com.liferay.portal.model.impl.OrgLaborImpl=...
  3. ...

@wincent
Copy link
Author

wincent commented Jun 16, 2020

Same test failures here as in #21 so assigning to you @izaera to check those out.

@jbalsas
Copy link

jbalsas commented Jun 22, 2020

Hey @izaera, do we need LPS-115485 commits in here? If we do, we'll need to wait for those to get merged to safely push this... if they aren't, could you take them out so we can work on this independently?

@izaera
Copy link
Collaborator

izaera commented Jun 22, 2020

@jbalsas

Hey @izaera, do we need LPS-115485 commits in here?

Yes, but only because they create the project folders with the gradle and bnd files. But other than that, nothing else is shared, so we may work on this PR independently from the other (that's why I made two PRs in first place).

It's only that we cannot forward this until the other gets merged which, on the other hand, wouldn't make too much sense.

…or a theme

The ThemeCSSVariableDescriptionsRegistryImpl class obtains CSS variables from
a /WEB-INF/css-variables.json file present inside the theme's WAR file.
We will use field liferayTheme.cssVariablesPath to store that value, and
'css-variables.json' will be the default value in case it is missing.
This is to avoid race conditions in case someone accesses the map in the middle
of an update.
Don't support localization in the service itself for now. We will see in the
future how we model it, but we don't want to couple it with the service itself,
since it is a very specific UI level thing.
First, we will use the Tokens-Path bundle header to get the path to the
JSON file inside the WAR and will use WEB-INF/tokens.json as default
value.

Then we will change the format of the JSON file to make it more
extensible and maintainable in the future.

Note that themes are WAR files, not OSGi bundles, so people need to add
the Tokens-Path header to the liferay-plugin-package.properties file to
make it appear in the MANIFEST.MF file of the generated OSGi bundle.

Also note that we check for the existence of a </theme> tag in the
liferay-look-and-feel.xml file to distinguish theme WAR files from
other type of WAR artifacts.
@izaera
Copy link
Collaborator

izaera commented Jun 24, 2020

@jbalsas I have force-pushed (because I needed to rebase) the requested changes. Do you mind having a look in case something else needs to be polished? Thx.

The real changes start at commit 2066d40 (i.e.: only the last three are new) with the last one being the more important. The rest remain the same, as in the previous PR.

I have implemented the change from css-variables.json to tokens.json even though we didn't get to an agreement in this thread...

@izaera
Copy link
Collaborator

izaera commented Jun 24, 2020

ci:test:sf

*/
public interface CSSVariableDescription {

public CSSVariableType getCSSVariableType();
Copy link

@jbalsas jbalsas Jun 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tokenType or simply type?

@liferay-continuous-integration
Copy link
Collaborator

✔️ ci:test:stable - 20 out of 20 jobs passed

✔️ ci:test:relevant - 50 out of 50 jobs passed in 1 hour 56 minutes

Click here for more details.

Base Branch:

Branch Name: master
Branch GIT ID: 0df14b8ed544d2c0286c4cc200703e3005e673ad

Copied in Private Modules Branch:

Branch Name: master-private
Branch GIT ID: 1eb935eac6911217dfe48eb2379d11fb23dd7f33

ci:test:stable - 20 out of 20 jobs PASSED
20 Successful Jobs:
ci:test:relevant - 50 out of 50 jobs PASSED
50 Successful Jobs:
For more details click here.

Copy link

@jbalsas jbalsas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @izaera, thanks for the changes! Seeing it it all starts to make sense in my mind...

The more I think about it the more I like the "Tokens" idea. It makes it all a bit more generic and flexible and it's actually easier to explain than CSSVariables.

Now that I think about it... since we've already merged this, chances are we won't be able to change it if Brian publishes, so maybe the first thing to do would be to add a .releng-ignore thingie to make sure we have time until the next release to flesh this out.

I'm trying to wrap up these changes with the ones we already pushed to master... sorry, getting hard to keep track of everything :(

I'd say we need something that's called css-variables which is the thing we definitely use to print the CSS variables.

That system should be fed with tokens or something like that and know how to transform them... that would allow us to add additional tokenTransformer approaches when needed and decouple implementation from concept.

Does this make sense?

@izaera
Copy link
Collaborator

izaera commented Jun 25, 2020

I'm trying to wrap up these changes with the ones we already pushed to master... sorry, getting hard to keep track of everything :(
I'd say we need something that's called css-variables which is the thing we definitely use to print the CSS variables.

I split it up into two pulls for that reason ;-). The first PR is css-variables for sure but this one, I'm not sure...

I would say this is CSS variables too because it only adds a service to discover which CSS variables a theme supports. And we know that they are CSS variables because it's a theme. However, I see place for tokens in a theme (for example inside templates, or predefined content) so I'm not sure what is best:

  1. Leave this as CSS variables and implement a new service in the future if we add more tokens
  2. Declare everything as a token (CSS variables and what may come next) and rename this whole PR

The only thing that scares about 2 is that tokens may be different things covered under the same abstraction in the future. For example: CSS variables now have a name and type. One of the types is color which is OK for a CSS variable but may be totally irrelevant for a content token (for example). You could argue that in that case you use string which is OK, but then more types can appear, and more attributes which are not the same for every type of token.

To sum up: I'm OK with naming this tokens if we at least have a clear idea of what other tokens may look like (i.e: if we can define what is a token and what isn't now, in advance).

@jbalsas
Copy link

jbalsas commented Jun 26, 2020

Putting this on hold momentarily since TokenDefinition is in a bit of a definition phase itself :)

@jbalsas
Copy link

jbalsas commented Jul 6, 2020

Pinging @ealonso, @ruben-pulido and @JorgeFerrer here so we can start this again. This is an initial PR we had before the StyleEditor epic started moving that does a bunch of things that might not be necessary.

While we discuss the JSON format I'm looking for input as to what to do in regards:

  • How to name this thing? tokens.json? design_system.json? theme.json?
  • This PR offered a way to retrieve that definition of a theme to be consumed. The question here is... should the definition be returned as:
    • Plain JSON Object
    • A specific Java model

@JorgeFerrer
Copy link

The name of the file must reflect as accurately as possible what it is, not what it's used for.
In this case, that means that it should have the word "token" in it, since what the file does is define tokens. An alternative term could be "css-variables" since that's at the end what a token is.

Since this is a definition of what the available tokens are, I believe that token-definition.json is a good name.

Thoughts?

I don't have a clear opinion on the JSON vs Java question right now, so I'd prefer other people to chime in on that one.

@jbalsas
Copy link

jbalsas commented Jul 7, 2020

Hey @izaera, I'm going to summarize my understanding of what we have and what I think we could build next to try to unblock this.

Please, correct me as needed! 🤗

frontend-css-variables modules

We currently have 2 frontend-css-variables-* modules (api and web) that are the ones responsible for registering and rendering values for scoped css variables.

theme token definition

Our next step would be to provide an interface to:

  • Enhance Base Themes with a token-definition.json
  • Provide APIs to:
    • Retrieve a TokenDefinitionJSON given a themeId
    • Retrieve a List of [themeID, tokenDefinition]. Not sure if this one's needed right now, though... so maybe better keep it out for now

Waiting on @ealonso and @ruben-pulido to answer #17 (comment), I think we could simply return the JSON or even String here. We can always enhance the API to model the TokenDefinition, but maybe it's too son for it. What do you think?

Based on this, I'd suggest:

  • Create a new module to enhance and extend base themes (wars) with TokenDefinition and provide the mentioned API.

@JorgeFerrer, does this seem aligned with Echo's roadmap? (can't ping Tarik since he still doesn't have a GitHub alias... but sending him a message on Slack)

@ealonso, @ruben-pulido, do you think this should be enough to implement the StyleBook creation or would you require something else?

@ealonso
Copy link

ealonso commented Jul 7, 2020

@jbalsas

Waiting on @ealonso and @ruben-pulido to answer #17 (comment), I think we could simply return the JSON or even String here. We can always enhance the API to model the TokenDefinition, but maybe it's too son for it. What do you think?

A String should be enough at this moment.

@ealonso, @ruben-pulido, do you think this should be enough to implement the StyleBook creation or would you require something else?

What do you mean with, this should be enough?

@jbalsas
Copy link

jbalsas commented Jul 7, 2020

What do you mean with, this should be enough?

So, once we do this we'll have:

  • A way to get a TokenDefinition for a theme. This should allow you to generate UI for those tokens and store values where you want.
  • A way to provide token values as ScopedCssVariable so the values in a StyleBook apply to a given scope. This should allow for stored StyleBooks to produce visible effects on pages

Do you think there's any additional Infrastructure or Component you'd like us to provide or work on?

For example, we discussed having an autogenerated UI which was discarded. Another thing that we could explore is providing a unified way of "previewing" changes. This is mostly tied to the UI, and likely some simple JS based on our past experience, but we can flesh it out more if needed.

@izaera
Copy link
Collaborator

izaera commented Jul 7, 2020

Please, correct me as needed!

LGTM :-)

@izaera
Copy link
Collaborator

izaera commented Jul 7, 2020

An alternative term could be "css-variables" since that's at the end what a token is.

If a token is really a CSS variable, I would call it CSS variable, since that way you don't need two terms.

However, I think token is higher level than CSS variable, since you may have an UI to define tokens that will be injected as CSS variables in themes, but could be injected as some other thing (for example, an inline style) in other place of the portal. Theoretically you could even use a color token to colorize images/logos.

Said that, the JSON file that goes inside a theme file, AFAIK, can only be considered as a description of CSS variables as they will only be used inside CSS rules (that is: the JSON file describes low level CSS variables, not high level UI tokens). Unless we plan to use them in templates, or other content inside theme bundles...

@jbalsas
Copy link

jbalsas commented Jul 7, 2020

Said that, the JSON file that goes inside a theme file, AFAIK, can only be considered as a description of CSS variables as they will only be used inside CSS rules (that is: the JSON file describes low level CSS variables, not high level UI tokens). Unless we plan to use them in templates, or other content inside theme bundles...

That JSON must have a way to map tokens with CSSVariables, but in no way is a description of CSS Variables.

Now that you mention it... one way to simplify what we're discussing over at https://github.com/jbalsas/liferay-portal/pull/2218 would be to split into 2 files... token-definition.json and css-var-mappings.json... 🤔 ... but then we'd need 2 APIs :D

@izaera
Copy link
Collaborator

izaera commented Jul 7, 2020

would be to split into 2 files... token-definition.json and css-var-mappings.json

But wouldn't that be a 1-1 mapping?

To elaborate more my view: my assumptions are that a theme bundle is deployed and, because it is a theme, it defines the CSS variables it lets override.

Then, some OSGi bundle in the portal reads those CSS variable definitions, interprets them as tokens, and shows an UI to the user to define those tokens (and possibly some tokens coming from other non-theme places).

Then, the OSGi bundle that showed the UI to define those tokens takes care of registering a ScopedCSSVariablesProvider to inject the values for the theme (and possibly other services to render the non-CSS tokens).

Does that makes sense?

I mean, why letting the theme know anything about tokens? What is the advantage?

@jbalsas
Copy link

jbalsas commented Jul 7, 2020

Does that makes sense?

It kinda does, yeah... but I think it's limiting in what we want to do for the future

I mean, why letting the theme know anything about tokens? What is the advantage?

I think @JorgeFerrer has a pyramid with this and what we want to accomplish 😂 . If you check Tarik's presentation, it was outlined there, and some things we'd need for the future might or might not be implemented through CSS Variables.

I think it's rather unfortunate that right now this is our only possible way forward. We shouldn't bank all our strategy (naming, APIs, roadmap) in this, though.

@izaera
Copy link
Collaborator

izaera commented Jul 7, 2020

We shouldn't bank all our strategy (naming, APIs, roadmap) in this, though.

That's why I was proposing to call it CSS variables (only inside themes) which is the only thing we have clear right now ;-).

However, given that we are only going to return a JSON string I will call it tokens for now and we'll see what happens in the future 😅

@ealonso
Copy link

ealonso commented Jul 7, 2020

So, once we do this we'll have:

  • A way to get a TokenDefinition for a theme. This should allow you to generate UI for those tokens and store values where you want.
  • A way to provide token values as ScopedCssVariable so the values in a StyleBook apply to a given scope. This should allow for stored StyleBooks to produce visible effects on pages

Do you think there's any additional Infrastructure or Component you'd like us to provide or work on?

I think it is good enough for the moment, once we start working with it, we can give you more feedback

@jbalsas
Copy link

jbalsas commented Jul 7, 2020

I think it is good enough for the moment, once we start working with it, we can give you more feedback

Great, thanks!!

@izaera, going to close this one since the solution will be pretty different... please link to this when you open a new one? 🙏

@izaera
Copy link
Collaborator

izaera commented Jul 10, 2020

This is hopefully the final PR -> brianchandotcom#91176

Linking for the record.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants