<div style="display: flex; flex-direction: column; width: 100%;"> <h1> You probably want to use LogList and not the singular Log component. </h1> <br> <div class="lui-log" data-controller="log" data-log-expanded-copy-text-value="Show details" data-log-collapsed-copy-text-value="Hide details"> <div class="lui-log__main"> <div class="lui-log__content"> <div class="lui-log__content__icon"> <div class="lui-tooltip hidden" data-controller="tooltips" data-tooltips-tippy-target-id-value="" data-tooltips-position-value="top" data-tooltips-interactive-value="false" > <div class="lui-tooltip__title"> Item </div> </div> <i class="lui-m_icon material-symbols-outlined" style="--lui-micon-size: 20px;"> package_2 </i> </div> <div class="lui-log__content__header"> <div class="lui-log__content__header__title" data-log-target="content"> <div class="lui-log__word">Protocol </div> <div class="lui-log__word">Answers </div> <div class="lui-log__word">saved.</div> </div> <div class="lui-log__content__header__description"> By System </div> <div class="lui-log__buttons"></div> </div> </div> <div class="lui-log__source_content"> 31/03/2026 10:45 </div> </div> </div></div>Log
Renders one timeline entry with content, metadata, optional expandable details and action buttons.
Related components
| Used components | Components where it is used |
|---|---|
LogList |
Item logs tab, financial logs modal/list |
StateLabel |
Block/source badge on the right side |
Button |
Expand/download/retry actions |
Usage rules
- ✅ Use
log_class+ optionalblockto keep iconography and right badge consistent. - ✅ Use
from_any_sourcefactories when converting backend payloads. - ✅ If passing
block, provide full shape{ name:, kind:, url: }. - ✅ Use
with_expand/with_expand_contentwhen you need details; it adds the expand button automatically. - ✅ Prefer
with_button(...)overwith_button_manual(...)unless you need a custom render flow. - ❌ Do not pass raw HTML from untrusted sources (prefer factory helpers and controlled content).
<div style="display: flex; flex-direction: column; width: 100%;"> <h1> You probably want to use LogList and not the singular Log component. </h1> <br> <%# log is a Hash, with the same structure as a Item's presenter logs %> <%= render LooposUi::Log.from_any_source(log) %></div>No notes provided.
No params configured.
Description
The Log component is a versatile component for displaying individual log entries with rich content, expandable sections, and interactive elements.
It's designed to work within the LogList component but can also be used standalone.
The component supports various content types including text with HTML formatting, entity tokens, expandable content, and action buttons.
Arguments
| Property | Default | Required | Description |
|---|---|---|---|
error |
false |
Whether this log represents an error state | |
user |
nil |
User that performed the action (falls back to "System" when blank) | |
date |
- |
✓ | Timestamp of the log entry |
log_class |
"item" |
Controls left icon (transition, email, sms, item, privacy, etc.) |
|
block |
nil |
Optional hash { name:, kind:, url: } rendered as a StateLabel on right |
Slots
For content related:
content(default slot). Supports rich text with HTMLexpand- Use viawith_expand { ... }orwith_expand_content(...); it auto-creates the "Show details" button.button- Use viawith_button(...); rendersLooposUi::Button.button_manual- Use viawith_button_manual { ... }for custom/manual action rendering.source- Populates the right side of the Log. Has polymorphic options:entity_token_source- Renders an EntityToken.source- Alternative interface you can build an entity token and pass in as a parameter.
title_token(polymorphic slot) Appears to the left of the log title.- Can render:
- an
EntityToken - an entity
- manual content
block hash details
When passing block, use:
{ name: "Validation", kind: "validation", # app/service/block kind url: "https://..." # required when block is provided}kind is now mapped to current flow icons, including tool/decision kinds like
tag, remove_tag, auto_validation, auto_accept_proposal, decision_*,
as well as app kinds (core, submission, hubs, validation, handling).
Factory System
The Log component includes a factory system that allows automatic creation of Log components from various data sources. This system provides a consistent interface for converting different data types into Log components.
How It Works
The factory system automatically detects the appropriate factory for a given data source and uses it to create a Log component.
This is done through the from_any_source class method:
log = LooposUi::Log.from_any_source(my_data_source)# Note you can also use the desired factory directlylog = LooposUi::Log::Factories::CoreItemLogPresenterHash.create(my_data_source)# orlog = LooposUi::Log::Factories::CoreItemLogPresenterHash.try_create(my_data_source)# returns NilLog if the factory can't create the log in production# raises error in developmentfrom_any_source signature:
LooposUi::Log.from_any_source(source)Implemented Factories
1. CoreItemLogPresenterHash
Purpose: Converts hash data from core item log presenters into Log components.
Data Source: Hash with the following structure:
{ time: DateTime, author: String, app_instance: { name: String, kind: String }, message: String, error: Boolean, log_class: String, # optional title: String, # optional extra_message: String, # optional block: { # optional (if present, must include all 3 keys) name: String, kind: String, url: String }}2. ScriptLog
Purpose: Converts LoopOs::ScriptLog objects into Log components.
Data Source: LoopOs::ScriptLog instances with properties like:
created_atuser_identificationlevelnamefilesinput_data
Automatic Features:
- Download Button: Automatically added when
filesare present - Retry Button: Automatically added when
input_datais present andscriptis a ScriptProxy with retry capability - Info Modal: Always included for detailed script information
- Retry Modal: Automatically created when retry button is present
Parameters:
script: ScriptProxy object that provides retry functionality (optional, for retry button)
Example:
script_proxy = LoopOs::Scripts::ScriptProxy.new(@loop_os_script, context: { retry_path_proc: ->(script_log) { rerun_script_script_path(@loop_os_script, script_log_id: script_log.id) }})log = LooposUi::Log::Factories::ScriptLog.new(script_log, script: script_proxy).createCreating Custom Factories
To create a custom factory, you need to:
Inherit from the base class:
class MyCustomFactory < LooposUi::Log::Factories::BaseImplement the can_create? class method:
def self.can_create?(source) # Return true if this factory can handle the given source source.is_a?(MyCustomModel)endImplement the create instance method:
def create LooposUi::Log.new( date: @source.created_at, user: @source.user_name, error: @source.error? ).with_content(@source.message)endAvailable Instance Variables in create method:
@source- The original data source passed to the factory constructor@view_context- Available when usingdefer_renderfor complex rendering
Example: Number Factory
Here's a complete example from the dummy app showing how to create a simple factory:
module Factories class ExampleLogNumberFactory < LooposUi::Log::Factories::Base def self.can_create?(source) source.is_a?(Integer) end def create LooposUi::Log.new( # random time +- 5days date: Time.now + (3*@source).hours + 60.minutes - rand(60).minutes, user: "random LogNumberFactory #{@source}", error: rand > 0.9 ).with_content("random message #{@source}") end endendDeferred Rendering
The factory create method must return a LooposUi::Log instance.
But this runs before the component is rendered, so if you need to call for render, you need to use defer_render and call the view_context.render method.
def create log = LooposUi::Log.new(...) log.defer_render do |log, view_context| # Complex rendering logic here view_context.render( SomeComponent.new(...)) end logendYou have some helpers methods available if you want to build up the log incrementally:
with_expand_content(content)
Note if you want to render HTML directly (e.g from a partial), you need to mark it as html_safe to avoid escaping.
Check for examples in the with_custom_expand example source code.