Extracting #5 into a separate issue, because as @f.mazeikis and @longwave pointed out: there's cache invalidation challenges there. So extracted A) preview-on-hover
into
🐛
Changes to code components are not visible in preview-on-hover-component-list until published
Active
.
wim leers → created an issue. See original summary → .
The fix is:
diff --git a/experience_builder.block_overrides.inc b/experience_builder.block_overrides.inc
index 1b396acf4..f41b45c38 100644
--- a/experience_builder.block_overrides.inc
+++ b/experience_builder.block_overrides.inc
@@ -172,7 +172,7 @@ function _experience_builder_render_js_component_from_block_element(array $block
// through an intermediary ephemeral Component entity?
$source = JsComponent::createConfigEntity($js_component)->getComponentSource();
- $build = $source->renderComponent(['props' => $props], $instance_uuid);
+ $build = $source->renderComponent(['props' => $props], $instance_uuid, TRUE);
assert($source instanceof JsComponent);
$source->setSlots($build, $slots);
return $build;
… except that the TRUE
cannot be hardcoded, but should happen only when appropriate, as described over at
#3512385-4: Changes to code components are not visible in A) preview-on-hover, B) global regions until published →
.
IMHO it's fine to hard-code this as-is, which would result in the auto-saved "block override" code component to be visible even OUTSIDE XB's UI. IMHO that's fine because this is a highly experimental feature that needs to be rewritten anyway into its own component source plugin, which means it's a waste of time to work on passing "XB preview or not" context into this. If we can figure it out in <30 mins, we can do that, otherwise should just commit that one-liner IMHO.
Thanks for confirming/clarifying.
The culprit is \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\BlockComponent::renderComponent()
's:
// ⚠️ Highly experimental: allow a Block plugin's Twig template to be
// overridden and rendered using an XB JavaScriptComponent instead.
$js_overrides = $this->entityTypeManager
->getStorage(JavaScriptComponent::ENTITY_TYPE_ID)
->loadByProperties([
'block_override' => $block->getBaseId(),
'status' => TRUE,
]);
// ⚠️ This assumes that all such overrides are accessible to all users! If
// that were not the case, presentation of the same block would vary between
// users, which is unacceptable.
// Therefore, this assumes that every user, even anonymous users, can access
// the rendered result of the found JavaScriptComponent.
// @see \Drupal\experience_builder\Element\AstroIsland::preRenderIsland()
// @see https://www.drupal.org/project/experience_builder/issues/3508694
if (count($js_overrides) == 1) {
$js_component_for_block_base_plugin = reset($js_overrides);
assert($js_component_for_block_base_plugin instanceof JavaScriptComponent);
$build['#theme'] = 'block__' . strtr($build['#base_plugin_id'], '-', '_') . '__as_js_component';
$build['#js_component'] = $js_component_for_block_base_plugin;
// Update cacheability.
$build['#cache']['tags'] = $js_component_for_block_base_plugin->getCacheTags();
$build['#cache']['contexts'] = $js_component_for_block_base_plugin->getCacheContexts();
$build['#cache']['max-age'] = $js_component_for_block_base_plugin->getCacheMaxAge();
}
… because that doesn't perform auto-save checking, because everything that ✨ Code Components as Block Overrides, step 1 Active did was a highly experimental hack 😅
Which is why to make this code component auto-save load, we're now needing to modify the block
ComponentSource
! 🤪
We can hack this further for now, but we really need to work to refactor all the ⚠️ Highly experimental
comments away: they're there for a reason!
Demoting priority, because such experimental features cannot be critical.
@tedbow in #8: Because https://git.drupalcode.org/project/experience_builder/-/merge_requests/7....
… which will grow into a larger, more abstract problem once XB supports more content entity types in 📌 Allow XB to be used on all node types Active .
Great work on this one — this looks very close! I wanted to merge it, but I couldn't help but agreeding with @larowlan's feedback WRT ✨ Send entity keys to the editor from the mount controller Active : https://git.drupalcode.org/project/experience_builder/-/merge_requests/7....
Raised 2 additional code clarity concerns (nullability instead of empty string, and autoSaveTitle
→ autoSaveLabel
, which makes more sense once Lee's feedback is addressed).
Reviewing…
Actually, the issue summary is way too ambiguous:
the updates do not take effect in preview
Which preview?
- The one in the code component editor?
- The one when hovering the component list?
- The XB canvas' preview of a content entity's component tree?
It kinda sounds like it's #1, but then the STR make it sound like it's #3.
If it's #3: does that "block override code component" (which is highly experimental!) happen to be placed in a page region, or in the content entity's XB field? If it's in a page region, this is a duplicate of 🐛 Changes to code components in global regions is not loaded until published Active .
⚠️ AFAICT this also affects the component preview upon hovering the list of available components:
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
index 4fbac989c..adb573f8d 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/BlockComponent.php
@@ -302,7 +302,7 @@ final class BlockComponent extends ComponentSourceBase implements ContainerFacto
return ['build' => []];
}
- return ['build' => $this->renderComponent([], $component->uuid())];
+ return ['build' => $this->renderComponent([], $component->uuid(), TRUE)];
}
/**
diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php b/src/Plugin/ExperienceBuilder/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php
index c55528b5b..39653166a 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php
@@ -603,7 +603,7 @@ abstract class GeneratedFieldExplicitInputUxComponentSourceBase extends Componen
return [
'source' => (string) $this->getSourceLabel(),
- 'build' => $this->renderComponent([self::EXPLICIT_INPUT_NAME => $default_props_for_default_markup], $component->uuid()),
+ 'build' => $this->renderComponent([self::EXPLICIT_INPUT_NAME => $default_props_for_default_markup], $component->uuid(), TRUE),
// Additional data only needed for SDCs.
// @todo UI does not use any other metadata - should `slots` move to top level?
'metadata' => ['slots' => $this->getSlotDefinitions()],
25-min deep dive suggests this is because
📌
Code Components should render with their auto-saved state(if any) when rendered in the XB UI
Active
only modified \Drupal\experience_builder\Controller\ApiLayoutController::buildPreviewRenderable()
to do:
$renderable = $item->toRenderable(TRUE);
which results in a JsComponent::renderComponent(isPreview: TRUE)
call, rendering the auto-save (draft).
But the page regions are not rendered in ::buildPreviewRenderable()
, but in \Drupal\experience_builder\Plugin\DisplayVariant\XbPageVariant::build()
, where there's still this:
$fiber = new \Fiber(fn() => $component_tree->toRenderable());
(note the absence of isPreview: TRUE
)
It'll be up to \Drupal\experience_builder\EventSubscriber\PreviewEnvelopeViewSubscriber::onViewPreviewEnvelope()
to pass that information to XbPageVariant
somehow.
AFAICT the only feasible approach is to rely on this bit in HtmlRenderer
:
// Instantiate the page display, and give it the main content.
$page_display = $this->displayVariantManager->createInstance($variant_id, $variant_configuration);
if (!$page_display instanceof PageVariantInterface) {
throw new \LogicException('Cannot render the main content for this page because the provided display variant does not implement PageVariantInterface.');
}
$page_display
->setMainContent($main_content)
->setTitle($title)
->addCacheableDependency($event);
// Some display variants need to be passed an array of contexts with
// values because they can't get all their contexts globally. For example,
// in Page Manager, you can create a Page which has a specific static
// context (e.g. a context that refers to the Node with nid 6), if any
// such contexts were added to the $event, pass them to the $page_display.
if ($page_display instanceof ContextAwareVariantInterface) {
$page_display->setContexts($event->getContexts());
}
IOW: update XbPageVariant
to implement ContextAwareVariantInterface
. Then do something like \Drupal\display_variant_test\EventSubscriber\TestPageDisplayVariantSubscriber::onSelectPageDisplayVariant()
to provide a yet-to-be-created "XB preview" context, which can then be respected by XbPageVariant
, and which would result in $page_display->setContrexts(['xb_preview' => TRUE]);
.
The only alternative I see: add a #xb_preview => TRUE
key-value pair to the "main content", which then is detected by XbPageVariant
.
Once you have selected a group you may want to copy/paste, dragging/rearrange them, or save them as a "Section".
This literally came up twice at DrupalCon!
wim leers → created an issue.
wim leers → created an issue.
See the patch I provided at #3508694-19: Permissions for XB config entity types → as a starting point.
As long as we agree on what the server will pass to the client in 📌 Pass current user's XB permissions to the XB UI Active , work on this could begin in parallel. But that probably makes little sense?
wim leers → created an issue. See original summary → .
Articulated a detailed plan for the top-priority issue here (see #3493070-11: SDC `enum` props should have translatable labels: use `meta:enum` → ), plus XB sibling issue ( ✨ Enum vales do not have translatable labels Active )
❌ I cannot reproduce the original steps to reproduce: creating a new Page
entity with an URL alias works fine:
… but I did manage to reproduce something kinda similar when I dug deeper? 😅
This is a pure front-end bug, because the same exact server-side response was provided in both of the requests I demonstrate in this GIF:
Also note that it's not only the path alias, it's every field's value!
P.S.: there's no auto-save entry for this content entity: neither before nor after performing the above test.
This also relates to 🌱 [META] 7. Content type templates — aka "default layouts" — clarify the tree+props data model Active : once content type template exists, the following scenario can occur:
- the XB content type template for "article nodes" has 3 subtrees that are unlocked, i.e. in which content creators can do whatever they want
- that means the XB field for each article node won't store a single tree anymore (under the root UUID), but 0–3 trees!
That works too :)
Otherwise users will need to have edit, create, and delete instead of one permission.
[…] Removing this makes the granular permissions more complicated.
I don't see how granting all 3 permissions is more complicated than granting a single "administer" permission? In fact, this is one of those things where Drupal's been historically misleading, because it has no way of conveying to the site administrator that "administer" really means "edit+create+delete".
You wrote it's "normal", but I'd argue it's a Drupalism that is not worth perpetuating? 😅
IOW: I agree with @lauriii's preference for both the simplicity (no "administer" permission) and the explicitness.
Note: this means the Page content entity type will NOT use the node module's access content.I think this is wrong in issue summary as we are using it, but it's provided by the system module.
You're right — I failed to remove this in #11 — my bad! 😅
Fixing markup.
And crediting @pdureau, for our collaboration at DrupalCon Atlanta to ensure XB and UI Patterns 2 are aligned 😊
Config schema changes necessary for XB's JavaScriptComponent
config entities attached to help get this going 👍
wim leers → created an issue.
Updated issue summary per @pdureau's #8.
Expanded it to a full implementation plan. Which is why it's clear this is definitely not .
#4 referenced https://github.com/adobe/jsonschema2md, so I went to look for an example there, and found one that's sufficiently silly to be fun:
"string_pattern": {
"type": "string",
"description": "A string following a regular expression",
"pattern": "^ba.$",
"examples": ["bar", "baz", "bat"],
"meta:enum": {
"baa": "the sounds of sheeps",
"bad": "German bathroom",
"bag": "holding device",
"bah": "humbug!",
"bam": "a loud sound",
"ban": "don't do this",
"bap": "a British soft bread roll",
"bas": "from ancient Egyptian religion, an aspect of the soul",
"bat": "…out of hell",
"bay": ", sitting by the dock of the"
},
— https://github.com/adobe/jsonschema2md/blob/f3b5773eb610130891503c1cf71b...
So let's use that one (or a variation thereof).
By the way,
✨
Add an icon management API
Active
also used meta:enum
:
* If an `enum` is set, then the select is used:
* - enum => #type = select and #options
* The key `meta:enum` is used to support description for each enum.
*
* @internal
* This API is experimental.
*/
class IconExtractorSettingsForm {
Per @pdureau at
#3493070-8: Enum values do not have translatable labels →
, this will also document meta:enum
to provide human-readable labels that also are translatable.
For example:
"string_pattern": {
"type": "string",
"description": "A string following a regular expression",
"pattern": "^ba.$",
"examples": ["bar", "baz", "bat"],
"meta:enum": {
"baa": "the sounds of sheeps",
"bad": "German bathroom",
"bag": "holding device",
"bah": "humbug!",
"bam": "a loud sound",
"ban": "don't do this",
"bap": "a British soft bread roll",
"bas": "from ancient Egyptian religion, an aspect of the soul",
"bat": "…out of hell",
"bay": ", sitting by the dock of the"
},
— https://github.com/adobe/jsonschema2md/blob/f3b5773eb610130891503c1cf71b...
Per discussion with @lauriii and @effulgentsia yesterday, @lauriii thinks this should not block stable, but should be possible to add post-1.0
.
Thanks!
@effulgentsia indicated that he expects at least #7.3 would be disabled
when >=1 entity or >=1 content entity field cannot be saved by the current user.
#20: ACK, context-free it is 👍 That means you've +1'd the _user_is_logged_in
proposal.
Could we add a new tab called "Components" and then add the two tabs under it?
Sure! :)
@tedbow FYI: the simpler, non-auto-save parts of this issue's scope have been extracted into 📌 [PP-1] Update `experience_builder.(experience_builder|api.layout.get) routes` to respect content entity update/field edit access of edited XB field Active .
@lauriii to confirm
- AFAICT the internal discussion between @tedbow and @lauriii leading to
📌
SdcController cleanup tasks
Active
failed to discuss field-level access control. 😅 And that's what this issue was originally opened for.
But it does specify the following functional requirements:
- Allow users with “Publish Experience Builder Content” to access the review changes panel, and to view all content that is about to be published.
- User with permission “Publish Experience Builder Content” must have access to edit all underlying entities at the time of publishing, in order to be able to publish changes.
- User with “Edit pages” without the “Publish Experience Builder Content” permission must be able to view changes to all content that they have edit access to.
which seems to imply that all necessary field-level access checks MUST be performed for the current user.
This is the fundamental question @tedbow raised → in #2.
- The list of unpublished changes in the popover does NOT need to indicate which ones the current user does not have sufficient permissions for to publish (1️⃣ in the screenshot):
The current user CANNOT know which parts they would be unable to publish, they would get an error message upon clicking . - The button in the popover does NOT need to be disabled if the current user does not have sufficient permissions to publish everything in the auto-save store (2️⃣ in the screenshot):
The current user can click , even if XB can technically already know they're guaranteed to get a 403 response (because >=1 entity cannot beupdate
d by the current user, or >=1 field cannot beedit
ed by the current user). - AFAIK "selective publishing" is a thing we intend to work on soon, and for that, we'd AFAIK need to pretty much solve both points 2 and 3. Do you want me to go ahead and create an issue for those?
Updated this per the plan at 📌 SdcController cleanup tasks Active .
Blocked on
📌
[PP-1] Update `experience_builder.(experience_builder|api.layout.get) routes` to respect content entity update/field edit access of edited XB field
Active
. If you follow the
steps to reproduce
📌
[PP-1] Update `experience_builder.(experience_builder|api.layout.get) routes` to respect content entity update/field edit access of edited XB field
Active
there, you'll see
This is because for the request that the XB UI makes for /xb/api/layout/node/2
, the server responds with:
{
"errors": [
{
"detail": "The current user is not allowed to update the field \u0027path\u0027.",
"source": {
"pointer": "entity_form_fields.path"
}
}
]
}
While this is true, the real problem here is that XB shouldn't try to auto-save fields that the current user cannot access (and FWIW, the tab aka the content entity form does not show the path
field's widget!).
Actually, this is AFAICT not blocked.
Crediting @tedbow because his comments on 📌 [PP-1] Add entity access checks to routes that deal with entities Postponed have helped me craft this. 😊
wim leers → created an issue.
Could we use
administer themes
permission for this?
Sure, that's fine.
@lauriii to confirm
-
It might make sense to even move this under "Appearance" but that's off-topic for this issue 🤔
That's a trivial move: instead of being listed at
/admin/structure
, add 2 new tabs at/admin/appearance
. It'd also allow us to clean up the long obsolete UI label.(Patch that implements this attached.)
-
Seems fine to allow viewing these to be tied to whatever parent context (e.g.,
access content
).There is no parent context when using the XB UI and fetching the list of
Component
andPattern
config entities. The XB UI just fetches them regardless of which thing you're editing, no matter if it's aPage
orNode
content entity, or aContentTypeTemplate
config entity (once 🌱 [META] 7. Content type templates — aka "default layouts" — clarify the tree+props data model Active is implemented).Either:
- it is acceptable that
Component
andPattern
config entities are retrieved context-free from the server to populate the choices available in the client. - the client
- first retrieves the edited XB component tree
- after it got a (2xx) response, it passes this context to the server again when fetching the
Component
andPattern
config entities - … while it'd return the exact same API response regardless of the context, it'd perform different access checking
This means that for now, it's oddly using different access checks to fetch the same data, but in the future it'd make it trivial to add context-aware (specifically, component tree host-aware) filtering logic.
Note too that this would cause worse front-end performance.
IMHO the context-free choice is the simplest, sanest choice for now.
- it is acceptable that
Outline of remaining plan.
Another important oversight spotted while refining the issue summary for #3508694: #3508694-14: Permissions for XB config entity types → , asked @lauriii to confirm.
Let's do this after 📌 Add access control for "code components" and "asset libraries", special case: instantiated code components must be accessible to *all* Active adds specific access control.
@lauriii to confirm
- The permissions listed in
📌
SdcController cleanup tasks
Active
do not include , so I added that in
📌
SdcController cleanup tasks
Active
.
there is one more permission, which would be required to access the
/admin/structure/component/status
UI. Please either confirm you agree, or propose an alternative. -
- Use a tiny subclass of default
\Drupal\Core\Entity\EntityAccessControlHandler
for theComponent
andPattern
config entity types, aka all XB config entity types that provide the basic building blocks for the XB UI. These must respect theadmin_permission
, except that viewing must always be allowed.For now, simply require the user to be authenticated (i.e. vary by the
user.roles:authenticated
) cache context).
In fact, it could be implemented more simply stil:
-
public function access(EntityInterface $entity, $operation, ?AccountInterface $account = NULL, $return_as_object = FALSE) { $account = $this->prepareUser($account); if ($operation === 'view') { $result = AccessResult::allowed(); return $return_as_object ? $result : $result->isAllowed(); } return parent::access($entity, $operation, $account, $return_as_object);
- Add the
_user_is_logged_in: 'TRUE'
route requirement to the
experience_builder.api.config.list
route.without this relaxation, the
administer patterns
andadminister components
permissions would be required for a regular Content Creator to be able to use XB at all.
- Use a tiny subclass of default
Oversights in 📌 Provide granular permissions for pages Active clarified by @lauriii. 👍
I'm terribly sorry, @penyaskito, but turns out that pretty much everything you've done on this issue/MR turns out to be unnecessary now that @lauriii has provided precise product requirements 🫣😅
But … I'm pretty sure this issue has helped @lauriii arrive at those clear conclusions!
AFAICT given the refined product requirements for XB permissions (see the new meta: 📌 SdcController cleanup tasks Active ) means that this issue can just:
- Use a tiny subclass of default
\Drupal\Core\Entity\EntityAccessControlHandler
for theComponent
andPattern
config entity types, aka all XB config entity types that provide the basic building blocks for the XB UI.For now, simply require the user to be authenticated (i.e. vary by the
user.roles:authenticated
) cache context). - Use the default
\Drupal\Core\Entity\EntityAccessControlHandler
for theJavaScriptComponent
,AssetLibrary
andPageRegion
config entity types, aka all XB config entity types that do not need to be available to all XB users.So that's basically just:
diff --git a/src/Entity/PageRegion.php b/src/Entity/PageRegion.php index e384258ed..773f4d2ee 100644 --- a/src/Entity/PageRegion.php +++ b/src/Entity/PageRegion.php @@ -23,7 +23,7 @@ use Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItemInstantiat * label_singular = @Translation("page region"), * label_plural = @Translation("page region"), * label_collection = @Translation("Page region"), - * admin_permission = "access administration pages", + * admin_permission = "administer page template", * entity_keys = { * "id" = "id", * "status" = "status", diff --git a/src/Entity/Pattern.php b/src/Entity/Pattern.php index 12e61d717..fb23d7895 100644 --- a/src/Entity/Pattern.php +++ b/src/Entity/Pattern.php @@ -22,7 +22,7 @@ use Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItemInstantiat * label_singular = @Translation("pattern"), * label_plural = @Translation("patterns"), * label_collection = @Translation("Patterns"), - * admin_permission = "access administration pages", + * admin_permission = "administer patterns", * entity_keys = { * "id" = "id", * "label" = "label",
- … but then the bulk of the remaining work is in updating the routes/controllers to respect that. Plus all the
testCannotDeleteWhenThereAreDependents()
work also remains relevant.
Updating XB's docs per #12: https://git.drupalcode.org/project/experience_builder/-/merge_requests/828
(This updates the docs that 📌 [PP-1] Diagram tying the product requirements + decisions together Postponed introduced.)
Closed 📌 Gate editing global regions behind 'administer blocks' permission in the UI Active in favor of this issue.
Per @lauriii's requirements, this will actually be covered by the permission — see 📌 SdcController cleanup tasks Active .
is a persona that did not exist until now. It's the in the Drupal CMS personas → .
Refinements:
- Added: (as in: XB
Component
config entities) - Put on hold, see issue summary for details.
- Put on hold, see issue summary for details.
Per @lauriii, we're actually indeed going to skip ownership permissions as indicated as a maybe (). @lauriii specifically listed these 4:
- View Pages (
view xb_page
) - Edit Pages (
edit xb_page
) - Delete Pages (
delete xb_page
) - Create Pages (
create xb_page
)
AFAICT this means @lauriii also intends us to remove HEAD's administer xb_page
permission.
@lauriii to confirm
- Am I extrapolating correctly that you think permissions should be as simple as possible, and hence we should refactor away the
administer xb_page
permission in this issue? - Do we indeed want to add instead of relying on
access content
? That permission used to be Node-specific, but has since been kind of generalized:
# Note that the 'access content' permission is moved to the Node section of the # permission form when the Node module is enabled. access content: title: 'View published content'
—
system.permissions.yml
an additional permission must be enabled for XB's landing pages to be viewable by anonymous users; theaccess content
permission is already typically assigned because it us used for: viewing nodes, viewingpublic://
files, file upload progress, and more.
Clarifying this is a planning issue and that it is postponed for now.
(It's not hard-blocked on 📌 SdcController cleanup tasks Active , but it is conceptually connected → .)
See 📌 SdcController cleanup tasks Active , which acknowledged a specific piece to be out of scope, and relates closely to this issue.
#12: Thanks for cross-posting here! Note that that does not solve the reuse challenge raised in #5.
Discussed this in detail with @lauriii. Converting this to a meta issue instead.
+1 — this seems like super low-hanging fruit!
This might be a stepping stone towards solving ✨ Feature request: Ability to specify minimum image resolution for an image upload Active .
wim leers → created an issue.
@lauriii has clarified his POV: there are plenty of (visual) page builders out there, that are design system-aware, that do not support this functionality. He thinks it's very important, but not a stable (1.0
) blocker.
If somebody in the community wants to pick this up, we'd more than welcome it, but the XB team won't be working on this before 1.0
.
I know now that it's definitely not considered a stable (1.0
blocker).
@pdureau When you said "form element", did you mean field type + field widget?
@ctrladel has been having conversations at DrupalCon about this problem space, and another aspect came up: the ability to limit the number of component instances in a slot: maybe there should be >=1, >=2, <5 — any such kind of limit. This exists for the Layout Builder ecosystem in contrib: https://www.drupal.org/project/layout_builder_limit
Kyle really impressed upon me how essential this is for building actual design systems in the real world. He's made it clear that it really needs to be part of a 1.0 release.
On top of this, @kristen pol also expressed to me the other day how when using Experience Builder with all of the components that https://www.drupal.org/project/demo_design_system → provides, the left-hand sidebar can be very overwhelming, which @larowlan just confirmed in #7.
@lauriii Given the above, can you confirm that you agree this is a stable blocker (aka 1.0
blocker)? If you disagree: why?
wim leers → created an issue.