Skip to content

Commit

Permalink
[Logs UI] Refactor Time and Position log stream state (#149052)
Browse files Browse the repository at this point in the history
## Summary

Closes #145137

Initially this issue was just going to include moving the time context
into the query state machine. However, this actually made working with
the dependant log position code a lot harder. As such the log position
code has also been moved to it's own state machine.

## 🕵️‍♀️ Reviewer hints and notable changes

- There are some comments left inline (*previous logic* notes might be
useful)

- There is now a new machine for dealing with Log Position state (target
position, latest position, visible positions).

- Time based context (time range, timestamps, refresh interval) is now
moved to the query machine (this will also make dealing with saved
queries easier).

- The page state machine is the only machine that the UI interacts with
(either reading context or sending events). The page state machine
co-ordinates forwarding necessary events to other internal machines.

- Ensure relevant notifications reach their targets, e.g. when time is
changed, positions should also update.

- [There is some documentation regarding URL state and
precedence](f9ca0f7).

- `updateContextInUrl` now always sets the full URL-relevant context in
the URL when called (since the `urlStateStorage.set()` call replaces the
whole key).

- Within the Log Stream Query state machine the `initialized` state node
is now modelled as a parallel state node, this is so `query` and `time`
can act independently (time needs to handle things like the refresh
interval, for example).

## 🕹 Testing

(Just some ideas)

- Can the time range be changed?
- Can the refresh interval be changed?
- Is state synchronised to the URL and to relevant Kibana services (time
filter service etc)?
- When streaming is enabled, are requests dispatched correctly on an
interval?
- Do positions update correctly whilst interacting with the stream?
(scrolling etc)
- Does the backwards compatibility of initialising from the URL work?

## 🎨 State machine diagrams

### Log stream page

![Screenshot 2023-02-06 at 15 29
27](https://user-images.githubusercontent.com/471693/217014687-1e1b6ae4-a7b5-4e3c-930f-5d9cf7406453.png)

### Log stream query

![Screenshot 2023-02-06 at 15 31
38](https://user-images.githubusercontent.com/471693/217014757-7c6b1788-65a2-460e-abb6-a1f9fee1131a.png)

### Log stream position

![Screenshot 2023-02-06 at 15 30
36](https://user-images.githubusercontent.com/471693/217014809-017a4b23-9a4a-4784-8ac3-36cbbecfd72e.png)

## ⚠️ Warnings

- [There is a known bug with
streaming](#136159 (comment)).
- [There is a known issue with a console
error](#149052 (comment))

---------

Co-authored-by: Felix Stürmer <[email protected]>
  • Loading branch information
Kerry350 and weltenwort authored Feb 21, 2023
1 parent eaa0884 commit 8583231
Show file tree
Hide file tree
Showing 38 changed files with 1,726 additions and 1,096 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/infra/docs/state_machines/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ implementation patterns:

- [Patterns for designing XState state machines](./xstate_machine_patterns.md)
- [Patterns for using XState with React](./xstate_react_patterns.md)
- [Patterns for working with URLs and URL precedence](./xstate_url_patterns_and_precedence.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# URL patterns and URL precedence

## Summary

When working with state it's common to synchronise a portion to the URL.

### Patterns

Within our state machines we begin in an `uninitialized` state, from here we move in to states that represent initialisation of intitial values. This may differ between machines depending on which Kibana services (if any) are relied on. It could also be possible to have a machine that merely has defaults and does not rely on services and URL state.

For example here is an example of our `uninitialized` state immediately transitioning to `initializingFromTimeFilterService`.

```ts
uninitialized: {
always: {
target: 'initializingFromTimeFilterService',
},
},
```

Our `initializingFromTimeFilterService` target might look something like this:

```ts
initializingFromTimeFilterService: {
on: {
INITIALIZED_FROM_TIME_FILTER_SERVICE: {
target: 'initializingFromUrl',
actions: ['updateTimeContextFromTimeFilterService'],
},
},
invoke: {
src: 'initializeFromTimeFilterService',
},
},
```

This invokes an (xstate) service to interact with the (Kibana) service and read values. We then receive an `INITIALIZED_FROM_TIME_FILTER_SERVICE` event, store what we need in context, and move to the next level of initialisation (URL).

As the target becomes `initializingFromUrl` we see much the same thing:

```ts
initializingFromUrl: {
on: {
INITIALIZED_FROM_URL: {
target: 'initialized',
actions: ['storeQuery', 'storeFilters', 'updateTimeContextFromUrl'],
},
},
invoke: {
src: 'initializeFromUrl',
},
},
```

Eventually we receive an `INITIALIZED_FROM_URL` event, values are stored in context, and we then move to the `initialized` state.

The code that interacts with the URL is in a file called `url_state_storage_service.ts` under the directory of the machine.

This is powerful because we could have as many layers as we need here, and we will only move to the `initialized` state at the end of the chain. Since the UI won't attempt to render content until we're in an `initialized` state we are safe from subtle race conditions where we might attempt to read a value too early.

## Precedence

In the Logs solution the order of precedence is as follows:

- Defaults
- Kibana services (time filter, query, filter manager etc)
- URL

That is to say the URL has most precedence and will overwrite defaults and service values.

### Log Stream

Within the Log Stream we have the following state held in the URL (and managed by xstate):

- Log filter
- Time range
- From
- To
- Refresh interval
- Pause
- Value
- Query
- Language
- Query
- Filters

- Log position
- Position
- Time
- Tiebreaker

#### Warning!

Due to legacy reasons the `logFilter` key should be initialised before the `logPosition` key. Otherwise the `logPosition` key might be overwritten before the `logFilter` code has had a chance to read from the key.

#### Backwards compatibility

The Log Stream does have some legacy URL state that needs to be translated for backwards compatibility. Here is an example of the previous legacy formats:

- Log filter
- Language
- Query

- Log filter (this version is older than language / query)
- Kind
- Expression

- Log position
- Start (now log filter > time range > from)
- End (now log filter > time range > to)
- StreamLive (now log filter > refresh interval > pause)
- Position
- Time (used to determine log filter > time range > from / to if start and end aren't set within legacy log position)
- Tiebreaker
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,4 @@
* 2.0.
*/

export * from './log_position_state';
export * from './replace_log_position_in_query_string';
export * from './use_log_position';
export type { LogPositionUrlState } from './use_log_position_url_state_sync';
Loading

0 comments on commit 8583231

Please sign in to comment.