Commit graph

16 commits

Author SHA1 Message Date
Hannah Mudge
b32f0fe1e8
[kbn-grid-layout] Store rows in object instead of array (#212965)
Closes https://github.com/elastic/kibana/issues/211930

## Summary

This PR makes it so that `kbn-grid-layout` stores its rows as an object
/ dictionary (`{ [key: string]: GridRowData }`) rather than an array
(`Array<GridRowData>`). This is a prerequisite for
https://github.com/elastic/kibana/issues/190381 , since it allows us to
re-order rows without re-rendering their contents. It also means that
deleting a row will no longer cause the rows below it to re-render,
since re-rendering is now dependant on the row's **ID** rather than the
row's order.

**Before**


https://github.com/user-attachments/assets/83651b24-a32c-4953-8ad5-c0eced163eb5


**After**


https://github.com/user-attachments/assets/9cef6dbc-3d62-46aa-bc40-ab24fc4e5556


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2025-03-04 13:34:39 -07:00
Hannah Mudge
e587187ffc
[kbn-grid-layout] Add ability to create, edit, and delete rows (#209193)
Closes https://github.com/elastic/kibana/issues/204849

## Summary

This PR adds the ability to create, edit, and delete sections / rows to
`kbn-grid-layout`:



https://github.com/user-attachments/assets/4831b289-2c71-42fb-851d-0925560e233a



Note that sections are still statically placed - dragging rows around
will be added in a follow-up PR, because it's a larger undertaking.
Since this feature is not available to users yet, it is okay to
implement this in stages like this.

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

Collapsible sections are not available on Dashboard yet and so there is
no user-facing risk to this PR.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com>
2025-02-24 18:29:00 +00:00
Hannah Mudge
200922a512
[kbn-grid-layout] Add useCustomDragHandle prop (#210463)
This PR **might** resolve
https://github.com/elastic/kibana/issues/207011 but we will need time
for the telemetry metrics to settle before we know for sure.

## Summary

This PR removes the conditional rendering of the default drag handle in
`kbn-grid-layout`, which has two benefits:

1. It removes the double render of `GridPanel` that was caused by
relying on the `dragHandleCount` to be updated in order to determine
whether the default drag handle should be rendered
2. The default drag handle no longer "flashes" when Dashboards are
loading and waiting for `dragHandleCount` to update
  
   - **Before:**
   

https://github.com/user-attachments/assets/30a032fc-4df3-42ce-9494-dd7f69637c03
      
   - **After:**
   

https://github.com/user-attachments/assets/db447911-cbe2-40dd-9a07-405d1e35a75d


Instead, the consumer of `kbn-grid-layout` is responsible for setting
the `useCustomDragHandle` prop to `true` when they want to use a drag
handle other than the default one.


When adding the `useCustomDragHandle` prop, I got annoyed that I had to
pass this prop all the way down to `grid_panel` - so I decided to swap
to using React context in this PR, as well. The API for the grid layout
component will most likely continue to grow, so this should make it
easier to manage the props.

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
2025-02-13 15:18:00 -07:00
Nathan Reese
05916056cd
[embeddable] make presentation interface names consistent (#205279)
PR cleans up presentation interface names for consistentency
* adds `$` suffix to all observables. For example, `dataLoading` =>
`dataLoading$`
* removes `Panel` naming convention from interface names since an api
may not be a panel, an api may be a dashboard. For example,
`PublisesPanelTitle` => `PublishesTitle`

#### Note to Reviewers
Pay special attention to any place where your application creates an
untyped API. In the example below, there is no typescript violation when
the parent returns `dataLoading` instead of `dataLoading$` since the
parent is not typed as `PublishesDataLoading`. Please check for
instances like these.

```
<ReactEmbeddableRenderer
  getParentApi={() => {
    dataLoading: new BehaviorSubject()
  }}
/>
```

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2025-01-22 10:46:01 -06:00
Hannah Mudge
5ee4297994
[Dashboard][kbn-grid-layout] Update styles (#206503)
Closes https://github.com/elastic/kibana/issues/204060

## Summary

This PR updates the styles used for `kbn-grid-layout` in Dashboard as
shown below.

- **Dragging**

    | Before | After |
    |--------|--------|
|
![image](https://github.com/user-attachments/assets/13161969-3eaf-4dce-bcf4-7b4850215816)
|
![image](https://github.com/user-attachments/assets/d76dc678-6277-4819-b554-f6b66b200c0c)
|
|
![image](https://github.com/user-attachments/assets/84d8d489-2240-4f10-809f-0aa30415f408)
|
![image](https://github.com/user-attachments/assets/573d71ad-71fb-47ab-a34e-66b845ecff67)
|

- **Resizing**

    | Before | After |
    |--------|--------|
|
![image](https://github.com/user-attachments/assets/79dfebd0-538b-4193-9b66-30961e9c7b21)
|
![image](https://github.com/user-attachments/assets/bc66ed35-83c4-4291-8cec-6ae8dda8f006)
|
|
![image](https://github.com/user-attachments/assets/d3fb5643-a77f-416f-9fc3-53af6225782a)
|
![image](https://github.com/user-attachments/assets/df2c65d5-af52-4848-b16c-f9f85abd5d9a)
|

As part of this work, I moved all aesthetic style logic out of the
`kbn-grid-layout` package and added support for Emotion to the
`GridLayout` component instead - this means that the consumer is
responsible for applying styles based on given classes, and
`kbn-grid-layout` is now less opinionated. The only styling kept in the
`kbn-grid-layout` package are those that handle layout-engine specific
functionality (positioning of panels, hiding edit actions in view mode,
etc).

In addition, I also updated the styles used in the grid example app and
added settings for dynamically changing the grid gutter size + row
height:




https://github.com/user-attachments/assets/c2f06db1-7041-412e-b546-86b102cc0770


### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

This PR has minimal risk, since it is primarily style changes.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2025-01-21 12:52:39 -07:00
Hannah Mudge
686571547f
[Dashboard][Collapsable Panels] Swap react-grid-layout for kbn-grid-layout (#205341)
Closes https://github.com/elastic/kibana/issues/190446

## Summary

This PR swaps out `react-grid-layout` for the new internal
`kbn-grid-layout` in the Dashboard plugin. This is the first major step
in making collapsible sections possible in Dashboard.

- **`react-grid-layout` (before)**:


https://github.com/user-attachments/assets/ca6ec059-7f4a-43fb-890e-7b72b781e50b

- **`kbn-grid-layout` (after)**:


https://github.com/user-attachments/assets/3d3de1f3-1afc-4e6b-93d6-9cc31a46e2cf

### Notable Improvements

- Better handling of resizing panels near the bottom of the screen
   
  | `react-grid-layout` | `kbn-grid-layout` |
  |--------|--------|
| ![Jan-09-2025
09-59-00](https://github.com/user-attachments/assets/75854b76-3ad7-4f06-9745-b03bde15f87a)
| ![Jan-09-2025
09-26-24](https://github.com/user-attachments/assets/f0fbc0bf-9208-4866-b7eb-988c7abc3e50)
|


- Auto-scroll when dragging / resizing panels near the top and bottom of
the screen, making it much easier to move panels around by larger
distances

  | `react-grid-layout` | `kbn-grid-layout` |
  |--------|--------|
| ![Jan-09-2025
10-01-30](https://github.com/user-attachments/assets/e3457e5e-3647-4024-b6e6-c594d6d3e1d7)
| ![Jan-09-2025
09-25-35](https://github.com/user-attachments/assets/3252bdec-2bbc-4793-b089-346866d4589b)
|

- More reliable panel positioning due to the use of CSS grid rather than
absolute positioning via pixels

  | `react-grid-layout` | `kbn-grid-layout` |
  |--------|--------|
| ![Screenshot 2025-01-09 at 9 32
52 AM](https://github.com/user-attachments/assets/06bd31a4-0a9f-4561-84c3-4cd96ba297b0)
| ![Screenshot 2025-01-09 at 9 35
14 AM](https://github.com/user-attachments/assets/573dab98-3fb9-4ef6-9f37-c4cf4d03ce52)
|

- Better performance when dragging and resizing (see
https://github.com/elastic/kibana/pull/204134 for a more thorough
explanation) and a smaller bundle size than `react-grid-layout`

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

This PR contains a significant change to the Dashboard layout engine,
which means that it carries a decent amount of risk for introducing new,
uncaught bugs with dragging / resizing panels and collision resolution.
That being said, `kbn-grid-layout` has been built **iteratively** with
plenty of testing along the way to reduce this risk.

## Release note
Improves Dashboard layout engine by switching to the internally
developed `kbn-grid-layout`.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com>
2025-01-14 14:51:14 -07:00
Gerard Soldevila
2be928c489
SKA: Categorise remaining packages (#205875)
## Summary

* Delete unused package `@kbn/formatters`
* Delete unused package `@kbn/response-ops-feature-flag-service`
* Flag `@kbn/generate-console-definitions` as `devOnly: true`
* Flag `@kbn/plugin-check` as `devOnly: true`
* Flag `@kbn/set-map` as `devOnly: true`
* Flag `@kbn/synthetics-private-location` as `devOnly: true`
* Categorise `@kbn/calculate-auto` as `platform/shared`
* Categorise `@kbn/charts-theme` as `platform/shared`
* Categorise `@kbn/saved-search-component` as `platform/shared`
* Categorise `@kbn/use-tracked-promise` as `platform/shared`
* Categorise `@kbn/charts-theme` as `platform/shared`
* Categorise `@kbn/response-ops-rule-form` as `platform/shared`
2025-01-08 22:20:14 +01:00
Hannah Mudge
1ff181c5df
[Dashboard][Collapsable Panels] New collision resolution algorithm (#204134)
Closes https://github.com/elastic/kibana/issues/191306

## Summary

This PR updates the collision resolution algorithm to handle the edge
case described in the linked issue.

In the old algorithm, we checked each panel for collisions a **single
time** - however, this caused a bug where panels could get re-ordered
depending on the order that we pushed panels down + how much distance we
were pushing them down by. While this algorithm was extremely efficient
(approximately $`O \left({ { n^2 + n } \over 2 }\right)`$ before
compaction where `n` is the number of panels), it was not as simple as
"fixing" the bug because, no matter what, there was always an edge case
that could not be handled without adding some sort of backtracking -
instead, we had to come up with a new algorithm.

### Example of Edge Case

To better understand how the old and new algorithms compare (and how
they work), consider a scenario where the grid layout has 8 columns and
the panels are laid out as follows:


![image](https://github.com/user-attachments/assets/94fbb8f6-b6b8-4f9e-af91-0128717cdffc)

Now, what happens when panel 4 (yellow) is dragged to the left so that
it overlaps both panel 3 (red) and panel 4 (blue)?


<details>
<summary><b>Old Algorithm</b></summary>

With the old algorithm, we get the following behaviour:
  
![Dec-16-2024
17-04-58](https://github.com/user-attachments/assets/9a920046-cf53-4dff-bfbe-6ed0aed05f77)

This is because we are **only** pushing the collided panels down
**once**. So, to describe what happens in detail, we start by gathering
up the panels **in order** (top-bottom, left-right, where dragged panels
take priority when rows are equal) - this results in an order of
`[panel4, panel3, panel1, panel2]`.

We start checking for collisions in this order - starting with panel 4
(yellow), we determine that it is colliding with panel 3 (red) and panel
2 (blue). We start with panel 3 (since we are once again doing this "in
order"), and we push it down by 5 rows (since this is the overlap of the
panel 3 and panel 4). Then, to "resolve" the second collision with panel
4, we push down panel 2 by 3 rows. This leaves the layout in the
following state:

   

![image](https://github.com/user-attachments/assets/47aa981e-9e4f-4c30-8570-abf6ba4a20ba)

Now, since panel 4 (yellow) is "resolved", we move on to the next panel
in the `[panel4, panel3, panel1, panel2]` order - panel 3 (red). Panel 3
now collides with panel 2, so we push panel 2 down by two rows. This is
the only collision with panel 3, so we check panel 1 and panel 2,
neither of which report collisions at this point. So the layout ends in
the following state:


![image](https://github.com/user-attachments/assets/4c5e3aca-399c-4075-9897-a5d2d73e9284)

So, what's the problem here? Well, panel 1 (green) is now "out of sync"
with the old panel ordering because it got "left behind" in the
collision resolution. Instead, we should have moved panel 1 by the same
amount as panel 2.

</details>


<details>
<summary><b>New Algorithm</b></summary>

With the new algorithm, we get the expected behaviour:

![Dec-16-2024
17-30-28](https://github.com/user-attachments/assets/4cd7d49d-138b-4b59-ac78-0ea2cbc86c3c)

If we run through the algorithm, we identify that the **first**
collision in the layout (based on left to right, top to bottom ordering,
with priority being the panel being dragged) is panel 4 (yellow) - so,
we enter the `while` loop and recurse to resolve all collisions with
panel 4 via `resolvePanelCollisions`. The recursion within
`resolvePanelCollisions` works as follows - get **all** collisions with
panel 4 (which, at this point, returns panel 3 (red) and panel 2 (blue))
and push them down by a single row, then recurse on **those** panels to
resolve any of the resulting collisions.

It's difficult to describe recursion, but let me try my best :) During
the collision resolution on panel 4, we start by pushing panel 3 down
and then recursively resolving the collisions on panel 3. Since pushing
panel 3 causes collisions with panel 1 and 2 (in order), we push them
both down by one row - starting with panel 1. We recurse to resolve the
collisions on panel 1, which has no collisions - then we do the same for
panel 2, which also reports no collisions. The collisions with panel 3
are "resolved" (i.e. we can "back out" of the recursion on panels 1 and
2), so we go back to resolving panel 4, push panel **2** down, and then
resolve the collisions on panel 2 - which reports no collisions. In
summary:


![image](https://github.com/user-attachments/assets/ca82ca05-673e-4bf1-b22f-e2aa1c7b6ba4)


Therefore, after the first iteration of the `while` loop, this results
in the following layout, where panel 3 (red) has been pushed down by one
row, panel 1 (green) has been pushed down by one row, and panel 2 (blue)
has been pushed down by two rows:


![image](https://github.com/user-attachments/assets/ec7c08a5-d0e9-46f7-bf3c-2eede7cef6fd)

We then return to the `while` loop and check if the layout has any other
collisions - and panel 4 is still our "first" collision! So we repeat
the process above and push both panel 4 and panel 2 down and recurse to
resolve the resulting collisions on those panels. This continues as
follows:

| Steps | Resulting Layout |
|--------|--------|
| <img width="400"
src="https://github.com/user-attachments/assets/d2691a1c-5f0b-4333-bfd4-3cc57e1a3098"/>
| <img width="400"
src="https://github.com/user-attachments/assets/85325df3-7fbf-46fa-af98-10bbd883cf8d"/>
|
| <img width="400"
src="https://github.com/user-attachments/assets/e31a400f-d0d1-408d-baec-efa708ad0c52"/>
| <img width="400"
src="https://github.com/user-attachments/assets/c81762d4-443b-451c-b8fc-dabaf4e32ba1"/>
|
| <img width="400"
src="https://github.com/user-attachments/assets/4292e4a9-4157-4d77-9b69-b0f6a07338ac"/>
| <img width="400"
src="https://github.com/user-attachments/assets/bdd2dad5-6531-4d56-b8c2-dfb121fb6b5b"/>
|
| <img width="400"
src="https://github.com/user-attachments/assets/6d194a89-3ed6-46a6-9dcc-2b3fa0de9942"/>
| <img width="400"
src="https://github.com/user-attachments/assets/7e0b0fb5-bacb-49ad-ac86-02665a779b59"/>
|

And so, after our fifth iteration of the `while` loop, the layout is
**no longer** reporting collisions - so, our layout is now fully
resolved!

</details>

### Performance Comparison

This algorithm is more-or-less the same as the algorithm used in
`react-grid-layout` - however, instead of running on **every frame**,
our layout engine only resolves collisions **as they happen** (hence why
the while loop is necessary). This results in much better rendering
performance.

To test this, I opened the Logs sample dashboard in both the example app
(which is obviously using `kbn-grid-layout`) and Dashboard (which is
still using `react-grid-layout`) and performed various drag actions on
both while recording their performance. For example, consider the
following capture of a drag that doesn't cause **any** collisions:

| `kbn-grid-layout` | `react-grid-layout` |
|--------|--------|
|
![image](https://github.com/user-attachments/assets/7fa1e9bb-e3dd-4f99-b011-0a46e53f0e88)
|
![image](https://github.com/user-attachments/assets/05e86a48-769d-4bae-ba48-d2fb3bdde90e)
|

You can see that, even with no collisions, the performance is slightly
better with `kbn-grid-layout` because we don't **ever** recurse on
collisions in this scenario. But it gets even better when actual
collisions happen - for example, consider the performance when the
dragged panel only causes a **single** collision:

| `kbn-grid-layout` | `react-grid-layout` |
|--------|--------| 
|
![image](https://github.com/user-attachments/assets/5e39a616-aa96-43be-a316-cb8fc4258b88)
|
![image](https://github.com/user-attachments/assets/520ab5b6-7049-4028-9d8c-63972aff9e92)
|


Versus when the dragged panel causes multiple collisions at the top of
the dashboard that causes a chain reaction of panels needing to be
pushed down:

| `kbn-grid-layout` | `react-grid-layout` |
|--------|--------| 
|
![image](https://github.com/user-attachments/assets/4d6b15c6-181a-4ea0-a179-09702b50411a)
|
![image](https://github.com/user-attachments/assets/363ef360-3c4f-4327-8031-18aca3833bce)
|

In all of the above scenarios, performance is improved overall **and**
there are fewer red "spikes" / bottlenecks due to [forced
reflow](https://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing?utm_source=devtools#avoid-forced-synchronous-layouts)
along the way. Most of the time saved is caused by significant
reductions to both `rendering` and `painting` time in `kbn-grid-layout`
when compared to `react-grid-layout`. While some of this improvement may
be explained by differences in the example grid plugin versus the
Dashboard plugin, this is enough proof that performance of
`kbn-grid-layout` is **at least as good as** `react-grid-layout` and so
swapping out the layout engine should not cause any performance
regressions.


### Other Notable Algorithm Attempts

I tried a few **drag/resize event specific** algorithms that **only
work** when an interaction event is happening - i.e. they rely on
information from the panel that is being interacted with in order to
function.

Note that, if we decided to go this route, we would still need some
generic collision resolution algorithm (such as the one proposed in this
PR) that we could use to resolve a layout when **no** interaction event
is happening. After all, we cannot assume that the grid layout that is
sent as a prop is valid (i.e. it **could** have overlapping panels **at
the start**, before any dragging and/or resizing has occurred) and we
need a way to verify + resolve it.

Unfortunately, both of the "interaction-event necessary" collision
resolution algorithms that I tried suffered from bugs so, while it might
be **possible** to make a more efficient algorithm for this, I
ultimately threw them away in favour of using the "generic" collision
resolution algorithm proposed in this PR.

That being said, I figured it was worth documenting the things that I
tried and why they failed.

<details>
<summary><b>Algorithm 1</b></summary>

I tried a modification of the **original** collision resolution
algorithm where, instead of **just** moving the colliding panels down by
the `rowOverlap` amount, we move **all** panels in or below the targeted
grid row down by the **height** of the panel being dragged. However, due
to the compaction algorithm ignoring **all** panels that have
collisions, you can end up in an infinite "push panels down" loop like
so:
 
![Dec-16-2024
15-31-22](https://github.com/user-attachments/assets/29364168-6bd5-4b62-839e-a63636af71f5)

This is because panel 3 is **always** colliding with panel 1 and 4, so
they never get pushed up to "close" the gaps. To try and get around
this, I modified the collision detection used for panel compaction so
that collisions only "stop" compaction if the collision occurs on a
panel **above** the given panel we are trying to compact. However, this
caused a **different** bug:

![Dec-16-2024
15-12-22](https://github.com/user-attachments/assets/34f23bfd-6a5d-4ff5-8421-10b7bab89551)

In the above GIF, consider what happens when panel 3 (red) targets row
3. As expected, we move all other panels (1, 2, and 4) down by a single
row. Then, when we reach the compaction step, we start by trying to push
panel 1 up (since it is now the top-left-most panel) - and, since we are
filtering out the collision with panel 3 (since it occurs "below" panel
1), it gets pushed all the way to the top. Then, we push panel 4 all the
way to the top (because it is also ignoring the collision with panel 3),
followed by pushing panel 2 up until it would collide with panel 3.
Unfortunately though, we didn't account for the full height of panel 4 -
so panel 3 is now stuck colliding with panel 4.

I modified this algorithm **further** to move the panels down by the
"row overlap" with the dragged panel rather than the dragged panel's
height - but this also suffers from a bug:

![Dec-16-2024
16-29-12](https://github.com/user-attachments/assets/950cb573-0caf-4c44-8f63-2fc465c2c497)
   
At which point, I abandoned this approach becase it felt like there were
too many edge cases that could not all be accounted for. It's possible
we could explore this further but, given the limitations of this
algorithm **only working** for resolving collisions given an interaction
event, I opted to move on.
</details>

<details>
<summary><b>Algorithm 2</b></summary>

I tried an algorithm where, similar to the algorithm proposed in this
PR, I recorded the panel IDs in each cell that a given panel occupied -
however, instead of moving things down row by row and handling the
resulting collisions, we moved **every** panel that was **below** the
targetted row down by the **height** of the panel being dragged. In
theory, the compaction algorithm would then take control of closing all
the resulting gaps.
  
Unfortunately, this didn't quite work in practice. Consider the
following:
  
![Dec-16-2024
10-17-21](https://github.com/user-attachments/assets/8157dabd-bb52-4089-9493-74cbdcf49e5e)
 
  
According to the algorithm defined above, panel 2 (blue) and panel 4
(yellow) both need to move down when panel 3 (red) is dragged to target
row 4 (remember that row **zero** is the first row), since they both
occupy this row - so we push both of them down by 1 (the height of panel
3). However, once we start compacting the layout in order to finish our
collision resolution, we hit a snag with panel 4 (yellow) - when we
start trying to compact panel 4, `getAllCollisionsWithPanel` returns
that **panel 3** is colliding with panel 4 and so we **break out** of
the compaction - hence why panel 4 continues to float. This then has a
snowball effect on all other panels as they report collisions and
therefore do not get pushed up.
</details>
 

<details>
<summary><b>Algorithm 3</b></summary>

This was the algorithm we **almost** went with, because it works! But it
suffered from two things - one, the code was complicated and difficult
to maintain and two, the collision resolution behaviour felt less
natural because it **always** favoured the dragged panel. This made it
too sensitive to pushing items down; so even if a panel was targeting
the bottom row of a different panel, it would push that panel down to
make room for the dragged panel.

In the simplest terms, this algorithm worked as follows: after creating
a 3D array representing the current grid (a 2D array of panel ID
arrays), for each row, while there are collisions, push panels down in
**reverse order** (bottom right to top left); continue until all rows
have no collisions. This algorithm hd an efficiency of approximately
$`O\left(r * c \right)`$ to set up the 3D array and $`O \left( c * h * n
\right)`$ to resolve the conflicts, where `r` is the number of rows, `c`
is the number of columns (minus 1), `h` is the height of the dragged
panel, and `n` is the number of panels. This results in an overall speed
of about $`O \left( (r * c) + (c * h * n) \right)`$

To better understand how this algorithm works, consider how it handles
the edge case described above:


<details>
<summary>Algorithm 3 - Edge Case Example</summary>

Rather than checking every **panel** for collisions a single time, we
check and resolve each **row** for collisions and resolve them. This
gives the following behaviour:


![Dec-16-2024
17-30-28](https://github.com/user-attachments/assets/60cf3347-707c-4bc2-99e5-0e042c4cbed3)

We start by creating a 3D array representing the grid - at the **first**
point of collision, it looks like:

|   | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

|--------|--------|--------|--------|--------|--------|--------|--------|--------|
| **0** | `[panel3]` | `[panel3]` | `[panel3]` | `[panel3, panel4]` |
`[panel4]` | `[panel4]` | `[panel4]` | `[]` |
| **1** | `[panel3]` | `[panel3]` | `[panel3]` | `[panel3, panel4]` |
`[panel4]` | `[panel4]` | `[panel4]` | `[]` |
| **2** | `[panel1]` | `[panel1]` | `[panel2]` | `[panel2, panel4]` |
`[panel4]` | `[panel4]` | `[panel4]` | `[]` |
| **3** | `[panel1]` | `[panel1]` | `[panel2]` | `[panel2, panel4]` |
`[panel4]` | `[panel4]` | `[panel4]` | `[]` |
| **4** | `[panel1]` | `[panel1]` | `[panel2]` | `[panel2, panel4]` |
`[panel4]` | `[panel4]` | `[panel4]` | `[]` |

Then, we start checking and resolving collisions, row by row. Starting
with row 0, we see that column 3 has a collision between panel 3 and
panel 4 - we **ignore** the panel being dragged (panel 4), so we push
down panel 3 by one row. Row 0 has no more collisions, so we move on to
row 1 and, following the same logic as above, we push down panel 3
again. This leaves the grid in the following state:


![image](https://github.com/user-attachments/assets/47ee1f8c-5107-41eb-8309-527e6fd773be)

|   | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

|--------|--------|--------|--------|--------|--------|--------|--------|--------|
| **0** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **1** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **2** | `[panel1, panel3]` | `[panel1, panel3]` | `[panel2, panel3]` |
`[panel3, panel2, panel4]` | `[panel4]` | `[panel4]` | `[panel4]` | `[]`
|
| **3** | `[panel1, panel3]` | `[panel1, panel3]` | `[panel2, panel3]` |
`[panel3, panel2, panel4]` | `[panel4]` | `[panel4]` | `[panel4]` | `[]`
|
| **4** | `[panel1]` | `[panel1]` | `[panel2]` | `[panel2, panel4]` |
`[panel4]` | `[panel4]` | `[panel4]` | `[]` |

Now, on row 2, we have collisions in columns zero through three, so we
push the panels in **reverse order** (i.e from the bottom right to the
top left) - this results in pushing panel 2 down, then panel 1, and
finally panel 3. We have to add a row in order to accomplish this
because otherwise we "lose" the bottom of panel 2, so our layout now
looks like this and row 2 has no more collisions:
   

![image](https://github.com/user-attachments/assets/b5697592-cb0e-4fac-90c9-6f3ade5e9db9)

|   | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

|--------|--------|--------|--------|--------|--------|--------|--------|--------|
| **0** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **1** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **2** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **3** | `[panel1, panel3]` | `[panel1, panel3]` | `[panel2, panel3]` |
`[panel3, panel2, panel4]` | `[panel4]` | `[panel4]` | `[panel4]` | `[]`
|
| **4** | `[panel1, panel3]` | `[panel1, panel3]` | `[panel2, panel3]` |
`[panel3, panel2, panel4]` | `[panel4]` | `[panel4]` | `[panel4]` | `[]`
|
| **5** | `[panel1]` | `[panel1]` | `[panel2]` | `[panel2]` | `[]` |
`[]` | `[]` | `[]` |

Continuing this behaviour, we push panel 2, panel 1, and panel 3 down in
row 3; then again in row 4. Once we reach and resolve row 5 (i.e. get
row 5 to the point where there are no collisions with panel 3), our
layout looks like so:


![image](https://github.com/user-attachments/assets/e59de766-a547-499a-a181-7841179032c0)
   
|   | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

|--------|--------|--------|--------|--------|--------|--------|--------|--------|
| **0** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **1** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **2** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **3** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **4** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **5** | `[panel3]` | `[panel3]` | `[panel3]` | `[panel3]` | `[]` |
`[]` | `[]` | `[]` |
| **6** | `[panel3, panel1]` | `[panel3, panel1]` | `[panel2, panel3]` |
`[panel2, panel3]` | `[]` | `[]` | `[]` | `[]` |
| **7** | `[panel1]` | `[panel1]` | `[panel2]` | `[panel2]` | `[]` |
`[]` | `[]` | `[]` |
| **8** | `[panel1]` | `[panel1]` | `[panel2]` | `[panel2]` | `[]` |
`[]` | `[]` | `[]` |

At this point, all collisions on panel 4 have been resolved. So, in row
6, we see that panels 2 and 1 are now colliding with panel 3 - so, we
push both of them down. At that point, row 6 does not have collisions so
we **don't** push panel 3 down any further - and our layout is resolved!
    

![image](https://github.com/user-attachments/assets/71869176-473f-4a48-8d0f-863b855628c4)

|   | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

|--------|--------|--------|--------|--------|--------|--------|--------|--------|
| **0** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **1** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **2** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **3** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **4** | `[]` | `[]` | `[]` | `[panel4]` | `[panel4]` | `[panel4]` |
`[panel4]` | `[]` |
| **5** | `[panel3]` | `[panel3]` | `[panel3]` | `[panel3]` | `[]` |
`[]` | `[]` | `[]` |
| **6** | `[panel3]` | `[panel3]` | `[panel3]` | `[panel3]` | `[]` |
`[]` | `[]` | `[]` |
| **7** | `[panel1]` | `[panel1]` | `[panel2]` | `[panel2]` | `[]` |
`[]` | `[]` | `[]` |
| **8** | `[panel1]` | `[panel1]` | `[panel2]` | `[panel2]` | `[]` |
`[]` | `[]` | `[]` |
| **9** | `[panel1]` | `[panel1]` | `[panel2]` | `[panel2]` | `[]` |
`[]` | `[]` | `[]` |

</details>

</details>


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

There are no risks to this PR, since all work is contained in the
`examples` plugin.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2025-01-02 16:04:20 -07:00
Tim Sullivan
99aa884fa0
Preparation for High Contrast Mode, Analytics Experience domains (#202608)
## Summary

**Reviewers: Please test the code paths affected by this PR. See the
"Risks" section below.**

Part of work for enabling "high contrast mode" in Kibana. See
https://github.com/elastic/kibana/issues/176219.

**Background:**
Kibana will soon have a user profile setting to allow users to enable
"high contrast mode." This setting will activate a flag with
`<EuiProvider>` that causes EUI components to render with higher
contrast visual elements. Consumer plugins and packages need to be
updated selected places where `<EuiProvider>` is wrapped, to pass the
`UserProfileService` service dependency from the CoreStart contract.

**NOTE:** **EUI currently does not yet support the high-contrast mode
flag**, but support for that is expected to come in around 2 weeks.
These first PRs are simply preparing the code by wiring up the
`UserProvideService`.

### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [X] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [X] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Risks

Does this PR introduce any risks? For example, consider risks like hard
to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified
risk. Invite stakeholders and evaluate how to proceed before merging.

- [ ] [medium/high] The implementor of this change did not manually test
the affected code paths and relied on type-checking and functional tests
to drive the changes. Code owners for this PR need to manually test the
affected code paths.
- [ ] [medium] The `UserProfileService` dependency comes from the
CoreStart contract. If acquiring the service causes synchronous code to
become asynchronous, check for race conditions or errors in rendering
React components. Code owners for this PR need to manually test the
affected code paths.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2024-12-12 13:16:07 -06:00
Hannah Mudge
2a76fe3ee4
[Dashboard] [Collapsable Panels] Add embeddable support (#198413)
Closes https://github.com/elastic/kibana/issues/190379

## Summary

This PR switches the example grid layout app to render embeddables as
panels rather than the simplified mock panel we were using previously.
In doing so, I had to add the ability for custom panels to add a custom
drag handle via the `renderPanelContents` callback - this required
adding a `setDragHandles` callback to the `ReactEmbeddableRenderer` that
could be passed down to the `PresentationPanel` component.




https://github.com/user-attachments/assets/9e2c68f9-34af-4360-a978-9113701a5ea2



#### New scroll behaviour

In https://github.com/elastic/kibana/pull/201867, I introduced a small
"ease" to the auto-scroll effect that happens when you drag a panel to
the top or bottom of the window. However, in that PR, I was using the
`smooth` scrolling behaviour, which unfortunately became **very**
jittery once I switched to embeddables rather than simple panels
(specifically in Chrome - it worked fine in Firefox).

The only way to prevent this jittery scroll was to switch to the default
scroll behaviour, but this lead to a very **abrupt** stop when the
scrollbar reached the top and/or bottom of the page - so, to give the
same "gentle" stop that the `smooth` scroll had, I decided to recreate
this effect by adding a slow down "ease" when close to the top or bottom
of the page:


https://github.com/user-attachments/assets/cb7bf03f-4a9e-4446-be4f-8f54c0bc88ac

This effect is accomplished via the parabola formula `y = a(x-h)2 + k`
and can be roughly visualized with the following, which shows that the
"speed up" ease happens at a much slower pace than the "slow down" ease:


![image](https://github.com/user-attachments/assets/02b4389c-fe78-448d-9c02-c4ec5e722d5e)




#### Notes about parent changes
As I investigated improving the efficiency of the grid layout with
embeddables, one of the main things I noticed was that the grid panel
was **always** remounted when moving a panel from one collapsible
section to another. This lead me (and @ThomThomson) down a rabbit hole
of React-reparenting, and we explored a few different options to see if
we could change the parent of a component **without** having it remount.

In summary, after various experiments and a whole bunch of research, we
determined that, due to the reconciliation of the React tree, this is
unfortunately impossible. So our priorities will instead have to move to
making the remount of `ReactEmbeddableRenderer` **as efficient as
possible** via caching, since the remount is inevitable.

### Checklist

- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

There are no risks to this PR, since the most significant work is
contained in the `examples` plugin. Some changes were made to the
presentation panel to allow for custom drag handles, but this isn't
actually used in Dashboard - so for now, this code is only called in the
example plugin, as well.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2024-12-10 11:14:31 -06:00
Marta Bondyra
d92aa91c3a
[Dashboard][Collapsable Panels] Responsive layout (#200771)
## Summary

Fixes https://github.com/elastic/kibana/issues/197714

### Key Features

#### Responsiveness

1. Adds a responsive view controlled by the `accessMode` prop. 
2. For the responsive version (in the `VIEW` mode), panels retain height
and are arranged based on screen order (left-to-right, top-to-bottom).
3. Interactivity (drag/resize) is disabled in `view` mode.

<img width="514" alt="Screenshot 2024-11-25 at 17 34 56"
src="https://github.com/user-attachments/assets/6a5a97aa-de9b-495a-b1de-301bc935a5ab">

#### Maximization
1. Supports expanded panel view using the `expandedPanelId` prop.
2. Interactivity (drag/resize) is disabled when a panel is expanded.

<img width="1254" alt="Screenshot 2024-11-25 at 17 35 05"
src="https://github.com/user-attachments/assets/c83014f6-18ad-435b-a59d-1d3ba3f80d84">

#### Considerations
1. CSS elements naming convention: Main component uses `kbnGrid` class,
with modifiers like `kbnGrid--nonInteractive`. For the drag handle of
`GridPanel` I used `kbnGridPanel__dragHandle` classname.
2. Classes vs. Inline Styles: I opted for using
`kbnGrid--nonInteractive` instead of adding one more subscription to
`GridPanel` to modify the styles inline. It's the first time in this
package that I used classes instead of inline styles for no-initial
styles setting.
3. Naming Convention: I opted for using the `expanded` word to describe
an expanded panel. Another one could be `maximized` as it's more used in
UI, but less in the legacy code.
4. Interactivity (drag/resize) is disabled in responsive mode but we
could consider to limit this to small viewports only (<768px).

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2024-11-28 19:31:09 +01:00
Hannah Mudge
549532240c
[Dashboard] [Collapsable Panels] Switch to using props (#200793)
Closes https://github.com/elastic/kibana/issues/200090

## Summary

This PR migrates the `GridLayout` component a more traditional React
design using **props** rather than providing an API. This change serves
two purposes:
1. It makes the eventual Dashboard migration easier, since it is more
similar to `react-grid-layout`'s implementation
3. It makes the `GridLayout` component less opinionated by moving the
logic for panel management (i.e. panel placement, etc) to the parent
component.

I tried to keep efficiency in mind for this comparison, and ensured that
we are still keeping the number of rerenders **o a minimum**. This PR
should not introduce **any** extra renders in comparison to the API
version.

### Checklist

- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

There are no risks to this PR, since all work is contained in the
`examples` plugin.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2024-11-21 10:27:56 -07:00
Hannah Mudge
a91427d71b
[Dashboard] [Collapsable Panels] Add panel management API (#195513)
Closes https://github.com/elastic/kibana/issues/190445

## Summary

This PR adds the first steps of a panel management API to the
`GridLayout` component:
- A method to delete a panel
- A method to replace a panel
- A method to add a panel with a given size and placement technique
(`'placeAtTop' | 'findTopLeftMostOpenSpace'`)
- Currently, we only support adding a panel to the first row, since this
is all that is necessary for parity with the current Dashboard layout
engine - we can revisit this decision as part of the [row
API](https://github.com/elastic/kibana/issues/195807).
- A method to get panel count
- This might not be necessary for the dashboard (we'll see), but I
needed it for the example plugin to be able to generate suggested panel
IDs. It's possible this will get removed 🤷
- The ability to serialize the grid layout state

I only included the bare minimum here that I know will be necessary for
a dashboard integration, but it's possible I missed some things and so
this API will most likely expand in the future.



https://github.com/user-attachments/assets/28df844c-5c12-40fd-b4f4-8fbd1a8abc20





### Serialization

With respect to serialization, there are still some open questions about
how we want to handle it from the Dashboard side - therefore, in this
PR, I opted to keep the serialization as simple as possible (i.e. both
the input and serialized output take identical forms for the
`GridLayout` component).

Our goal is to keep `kbn-grid-layout` as **generic** as possible so,
while I considered making the serialize method return the form that the
Dashboard expects, I ultimately decided against that; instead, I think
Dashboard should be responsible for taking the grid layout's serialized
form and turning it into a dashboard-specific serialization of a grid
layout and vice-versa for deserializing and sending the initial layout
to the `GridLayout` component.

The dashboard grid layout serialization will be tackled as part of
https://github.com/elastic/kibana/issues/190446, where it's possible my
opinion might change :) This is just a first draft of the
`kbn-grid-layout` API, after all.

### Example Grid Layout

In the grid layout example plugin, I integrated the API by adding some
pretty bare-bones buttons to each panel in order to ensure the API works
as expected - that being said, I didn't worry too much about the design
of these things and so it looks pretty ugly 😆 My next step is
https://github.com/elastic/kibana/issues/190379, where I will have to
integrate the grid layout API with the embeddable actions, at which
point the design will be improved - so this is a very temporary state
🙇

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)


### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2024-11-05 11:11:11 -07:00
Luke Elmers
b6287708f6
Adds AGPL 3.0 license (#192025)
Updates files outside of x-pack to be triple-licensed under Elastic
License 2.0, AGPL 3.0, or SSPL 1.0.
2024-09-06 19:02:41 -06:00
Devon Thomson
b02f6dbcc5
[Collapsable Panels] Better Scrolling for kbn grid layout (#191120)
changes `kbn grid layout` to allow better scrolling behaviour.
2024-08-27 13:36:33 -04:00
Devon Thomson
7290824e74
[Dashboard] New layout engine (#174132)
Introduces a new performant and simple drag & drop layout engine for Kibana which uses HTML5 and CSS and and has **no external dependencies**.
2024-08-15 17:51:43 -04:00