mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[Dashboard Navigation] Swap SASS for Emotion (#211124)](https://github.com/elastic/kibana/pull/211124) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Hannah Mudge","email":"Heenawter@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-02-21T15:07:23Z","message":"[Dashboard Navigation] Swap SASS for Emotion (#211124)\n\nPart of https://github.com/elastic/kibana/issues/207852\n\n## Summary\n\nThis PR migrates all `*.scss` files in the Links plugin to Emotion.\nTesting should simply verify that this PR does not introduce any style\nchanges.\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenario\n- [x] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n### Identify risks\n\nAny risks associated with this PR are purely cosmetic, since it contains\nexclusively style-related changes.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"85f4f4d5b4bdd2a91f1138cd5f660c88d729a3c8","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Presentation","loe:small","release_note:skip","impact:high","Project:Dashboard Navigation","backport:version","v9.1.0","v8.19.0"],"title":"[Dashboard Navigation] Swap SASS for Emotion","number":211124,"url":"https://github.com/elastic/kibana/pull/211124","mergeCommit":{"message":"[Dashboard Navigation] Swap SASS for Emotion (#211124)\n\nPart of https://github.com/elastic/kibana/issues/207852\n\n## Summary\n\nThis PR migrates all `*.scss` files in the Links plugin to Emotion.\nTesting should simply verify that this PR does not introduce any style\nchanges.\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenario\n- [x] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n### Identify risks\n\nAny risks associated with this PR are purely cosmetic, since it contains\nexclusively style-related changes.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"85f4f4d5b4bdd2a91f1138cd5f660c88d729a3c8"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/211124","number":211124,"mergeCommit":{"message":"[Dashboard Navigation] Swap SASS for Emotion (#211124)\n\nPart of https://github.com/elastic/kibana/issues/207852\n\n## Summary\n\nThis PR migrates all `*.scss` files in the Links plugin to Emotion.\nTesting should simply verify that this PR does not introduce any style\nchanges.\n\n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenario\n- [x] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n### Identify risks\n\nAny risks associated with this PR are purely cosmetic, since it contains\nexclusively style-related changes.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"85f4f4d5b4bdd2a91f1138cd5f660c88d729a3c8"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Hannah Mudge <Heenawter@users.noreply.github.com>
This commit is contained in:
parent
bd69e3602a
commit
75ca9340ca
13 changed files with 408 additions and 442 deletions
|
@ -1,38 +0,0 @@
|
|||
@import '../../../../../core/public/mixins';
|
||||
|
||||
@keyframes euiFlyoutOpenAnimation {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes euiFlyoutCloseAnimation {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin euiFlyout {
|
||||
height: calc(100vh - var(--euiFixedHeadersOffset, 0));
|
||||
position: fixed;
|
||||
display: flex;
|
||||
inline-size: 50vw;
|
||||
z-index: $euiZFlyout;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
border-left: $euiBorderThin;
|
||||
background: $euiColorEmptyShade;
|
||||
min-width: ($euiSizeXL * 13) + $euiSizeS; // 424px
|
||||
}
|
|
@ -14,12 +14,13 @@ import { createEvent, fireEvent, render, screen } from '@testing-library/react';
|
|||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { LINKS_VERTICAL_LAYOUT } from '../../../common/content_management';
|
||||
import { DashboardLinkComponent } from './dashboard_link_component';
|
||||
import { DashboardLinkComponent, DashboardLinkProps } from './dashboard_link_component';
|
||||
import { DashboardLinkStrings } from './dashboard_link_strings';
|
||||
import { getMockLinksParentApi } from '../../mocks';
|
||||
import { ResolvedLink } from '../../types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import { EuiThemeProvider } from '@elastic/eui';
|
||||
|
||||
function createMockLinksParent({
|
||||
initialQuery,
|
||||
|
@ -52,6 +53,37 @@ describe('Dashboard link component', () => {
|
|||
description: 'Dashboard 1 description',
|
||||
};
|
||||
|
||||
const renderComponent = (overrides?: Partial<DashboardLinkProps>) => {
|
||||
const parentApi = createMockLinksParent({});
|
||||
const { rerender, ...rtlRest } = render(
|
||||
<EuiThemeProvider>
|
||||
<DashboardLinkComponent
|
||||
link={resolvedLink}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
{...overrides}
|
||||
/>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
return {
|
||||
...rtlRest,
|
||||
rerender: (newOverrides: Partial<DashboardLinkProps>) => {
|
||||
return rerender(
|
||||
<EuiThemeProvider>
|
||||
<DashboardLinkComponent
|
||||
link={resolvedLink}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
{...overrides}
|
||||
{...newOverrides}
|
||||
/>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
window.open = jest.fn();
|
||||
});
|
||||
|
@ -62,13 +94,7 @@ describe('Dashboard link component', () => {
|
|||
|
||||
test('by default uses navigate to open in same tab', async () => {
|
||||
const parentApi = createMockLinksParent({});
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
link={resolvedLink}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
renderComponent({ parentApi });
|
||||
|
||||
// renders dashboard title
|
||||
const link = screen.getByTestId('dashboardLink--foo');
|
||||
|
@ -92,14 +118,7 @@ describe('Dashboard link component', () => {
|
|||
});
|
||||
|
||||
test('modified click does not trigger event.preventDefault', async () => {
|
||||
const parentApi = createMockLinksParent({});
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
link={resolvedLink}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
renderComponent();
|
||||
const link = screen.getByTestId('dashboardLink--foo');
|
||||
const clickEvent = createEvent.click(link, { ctrlKey: true });
|
||||
const preventDefault = jest.spyOn(clickEvent, 'preventDefault');
|
||||
|
@ -109,16 +128,14 @@ describe('Dashboard link component', () => {
|
|||
|
||||
test('openInNewTab uses window.open, not navigateToApp, and renders external icon', async () => {
|
||||
const parentApi = createMockLinksParent({});
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
...resolvedLink,
|
||||
options: { ...DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS, openInNewTab: true },
|
||||
}}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
renderComponent({
|
||||
link: {
|
||||
...resolvedLink,
|
||||
options: { ...DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS, openInNewTab: true },
|
||||
},
|
||||
parentApi,
|
||||
});
|
||||
|
||||
const link = screen.getByTestId('dashboardLink--foo');
|
||||
expect(link).toBeInTheDocument();
|
||||
// external link icon is rendered
|
||||
|
@ -149,16 +166,14 @@ describe('Dashboard link component', () => {
|
|||
to: 'now',
|
||||
});
|
||||
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
...resolvedLink,
|
||||
options: DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS,
|
||||
}}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
renderComponent({
|
||||
link: {
|
||||
...resolvedLink,
|
||||
options: DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS,
|
||||
},
|
||||
parentApi,
|
||||
});
|
||||
|
||||
expect(parentApi.locator?.getRedirectUrl).toBeCalledWith({
|
||||
dashboardId: '456',
|
||||
timeRange: { from: 'now-7d', to: 'now' },
|
||||
|
@ -185,19 +200,17 @@ describe('Dashboard link component', () => {
|
|||
to: 'now',
|
||||
});
|
||||
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
...resolvedLink,
|
||||
options: {
|
||||
...DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS,
|
||||
useCurrentDateRange: false,
|
||||
},
|
||||
}}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
renderComponent({
|
||||
link: {
|
||||
...resolvedLink,
|
||||
options: {
|
||||
...DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS,
|
||||
useCurrentDateRange: false,
|
||||
},
|
||||
},
|
||||
parentApi,
|
||||
});
|
||||
|
||||
expect(parentApi.locator?.getRedirectUrl).toBeCalledWith({
|
||||
dashboardId: '456',
|
||||
filters: initialFilters,
|
||||
|
@ -223,19 +236,17 @@ describe('Dashboard link component', () => {
|
|||
to: 'now',
|
||||
});
|
||||
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
...resolvedLink,
|
||||
options: {
|
||||
...DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS,
|
||||
useCurrentFilters: false,
|
||||
},
|
||||
}}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
renderComponent({
|
||||
link: {
|
||||
...resolvedLink,
|
||||
options: {
|
||||
...DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS,
|
||||
useCurrentFilters: false,
|
||||
},
|
||||
},
|
||||
parentApi,
|
||||
});
|
||||
|
||||
expect(parentApi.locator?.getRedirectUrl).toBeCalledWith({
|
||||
dashboardId: '456',
|
||||
timeRange: { from: 'now-7d', to: 'now' },
|
||||
|
@ -244,19 +255,14 @@ describe('Dashboard link component', () => {
|
|||
});
|
||||
|
||||
test('shows an error when fetchDashboard fails', async () => {
|
||||
const parentApi = createMockLinksParent({});
|
||||
renderComponent({
|
||||
link: {
|
||||
...resolvedLink,
|
||||
title: 'Error fetching dashboard',
|
||||
error: new Error('not found'),
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
...resolvedLink,
|
||||
title: 'Error fetching dashboard',
|
||||
error: new Error('not found'),
|
||||
}}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
const link = await screen.findByTestId('dashboardLink--foo--error');
|
||||
expect(link).toHaveTextContent(DashboardLinkStrings.getDashboardErrorLabel());
|
||||
});
|
||||
|
@ -266,17 +272,14 @@ describe('Dashboard link component', () => {
|
|||
parentApi.savedObjectId$ = new BehaviorSubject<string | undefined>('123');
|
||||
parentApi.title$ = new BehaviorSubject<string | undefined>('current dashboard');
|
||||
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
...resolvedLink,
|
||||
destination: '123',
|
||||
id: 'bar',
|
||||
}}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
renderComponent({
|
||||
link: {
|
||||
...resolvedLink,
|
||||
destination: '123',
|
||||
id: 'bar',
|
||||
},
|
||||
parentApi,
|
||||
});
|
||||
|
||||
const link = screen.getByTestId('dashboardLink--bar');
|
||||
expect(link).toHaveTextContent('current dashboard');
|
||||
|
@ -286,19 +289,13 @@ describe('Dashboard link component', () => {
|
|||
});
|
||||
|
||||
test('shows dashboard title and description in tooltip', async () => {
|
||||
const parentApi = createMockLinksParent({});
|
||||
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
...resolvedLink,
|
||||
title: 'another dashboard',
|
||||
description: 'something awesome',
|
||||
}}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
renderComponent({
|
||||
link: {
|
||||
...resolvedLink,
|
||||
title: 'another dashboard',
|
||||
description: 'something awesome',
|
||||
},
|
||||
});
|
||||
|
||||
const link = screen.getByTestId('dashboardLink--foo');
|
||||
await userEvent.hover(link);
|
||||
|
@ -315,48 +312,38 @@ describe('Dashboard link component', () => {
|
|||
savedObjectId$: new BehaviorSubject<string | undefined>('123'),
|
||||
};
|
||||
|
||||
const { rerender } = render(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
...resolvedLink,
|
||||
destination: '123',
|
||||
id: 'bar',
|
||||
}}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
const { rerender } = renderComponent({
|
||||
link: {
|
||||
...resolvedLink,
|
||||
destination: '123',
|
||||
id: 'bar',
|
||||
},
|
||||
parentApi,
|
||||
});
|
||||
|
||||
expect(await screen.findByTestId('dashboardLink--bar')).toHaveTextContent('old title');
|
||||
|
||||
parentApi.title$.next('new title');
|
||||
rerender(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
...resolvedLink,
|
||||
destination: '123',
|
||||
id: 'bar',
|
||||
label: undefined,
|
||||
}}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
rerender({
|
||||
link: {
|
||||
...resolvedLink,
|
||||
destination: '123',
|
||||
id: 'bar',
|
||||
label: undefined,
|
||||
},
|
||||
});
|
||||
expect(await screen.findByTestId('dashboardLink--bar')).toHaveTextContent('new title');
|
||||
});
|
||||
|
||||
test('can override link label', async () => {
|
||||
const label = 'my custom label';
|
||||
const parentApi = createMockLinksParent({});
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
...resolvedLink,
|
||||
label,
|
||||
}}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
renderComponent({
|
||||
link: {
|
||||
...resolvedLink,
|
||||
label,
|
||||
},
|
||||
});
|
||||
|
||||
const link = screen.getByTestId('dashboardLink--foo');
|
||||
expect(link).toHaveTextContent(label);
|
||||
await userEvent.hover(link);
|
||||
|
@ -369,18 +356,15 @@ describe('Dashboard link component', () => {
|
|||
const parentApi = createMockLinksParent({});
|
||||
parentApi.savedObjectId$ = new BehaviorSubject<string | undefined>('123');
|
||||
|
||||
render(
|
||||
<DashboardLinkComponent
|
||||
link={{
|
||||
...resolvedLink,
|
||||
destination: '123',
|
||||
id: 'bar',
|
||||
label: customLabel,
|
||||
}}
|
||||
layout={LINKS_VERTICAL_LAYOUT}
|
||||
parentApi={parentApi}
|
||||
/>
|
||||
);
|
||||
renderComponent({
|
||||
link: {
|
||||
...resolvedLink,
|
||||
destination: '123',
|
||||
id: 'bar',
|
||||
label: customLabel,
|
||||
},
|
||||
parentApi,
|
||||
});
|
||||
|
||||
const link = screen.getByTestId('dashboardLink--bar');
|
||||
expect(link).toHaveTextContent(customLabel);
|
||||
|
|
|
@ -10,34 +10,33 @@
|
|||
import classNames from 'classnames';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { EuiListGroupItem } from '@elastic/eui';
|
||||
import { EuiListGroupItem, UseEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { DashboardLocatorParams } from '@kbn/dashboard-plugin/public';
|
||||
import {
|
||||
DashboardDrilldownOptions,
|
||||
DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS,
|
||||
} from '@kbn/presentation-util-plugin/public';
|
||||
import { Query, isFilterPinned } from '@kbn/es-query';
|
||||
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import {
|
||||
DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS,
|
||||
DashboardDrilldownOptions,
|
||||
} from '@kbn/presentation-util-plugin/public';
|
||||
|
||||
import { isFilterPinned, Query } from '@kbn/es-query';
|
||||
import {
|
||||
DASHBOARD_LINK_TYPE,
|
||||
LinksLayoutType,
|
||||
LINKS_VERTICAL_LAYOUT,
|
||||
LinksLayoutType,
|
||||
} from '../../../common/content_management';
|
||||
import { trackUiMetric } from '../../services/kibana_services';
|
||||
import { DashboardLinkStrings } from './dashboard_link_strings';
|
||||
import { LinksParentApi, ResolvedLink } from '../../types';
|
||||
import { DashboardLinkStrings } from './dashboard_link_strings';
|
||||
|
||||
export const DashboardLinkComponent = ({
|
||||
link,
|
||||
layout,
|
||||
parentApi,
|
||||
}: {
|
||||
export interface DashboardLinkProps {
|
||||
link: ResolvedLink;
|
||||
layout: LinksLayoutType;
|
||||
parentApi: LinksParentApi;
|
||||
}) => {
|
||||
}
|
||||
|
||||
export const DashboardLinkComponent = ({ link, layout, parentApi }: DashboardLinkProps) => {
|
||||
const [
|
||||
parentDashboardId,
|
||||
parentDashboardTitle,
|
||||
|
@ -155,6 +154,7 @@ export const DashboardLinkComponent = ({
|
|||
color="text"
|
||||
{...onClickProps}
|
||||
id={id}
|
||||
css={styles}
|
||||
showToolTip={true}
|
||||
toolTipProps={{
|
||||
title: tooltipTitle,
|
||||
|
@ -179,3 +179,46 @@ export const DashboardLinkComponent = ({
|
|||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = ({ euiTheme }: UseEuiTheme) =>
|
||||
css({
|
||||
// universal current dashboard link styles
|
||||
'&.linkCurrent': {
|
||||
borderRadius: 0,
|
||||
cursor: 'default',
|
||||
'& .euiListGroupItem__text': {
|
||||
color: euiTheme.colors.textPrimary,
|
||||
},
|
||||
},
|
||||
|
||||
// vertical layout - current dashboard link styles
|
||||
'.verticalLayoutWrapper &.linkCurrent::before': {
|
||||
// add left border for current dashboard
|
||||
content: "''",
|
||||
position: 'absolute',
|
||||
height: '75%',
|
||||
width: `calc(.5 * ${euiTheme.size.xs})`,
|
||||
backgroundColor: euiTheme.colors.primary,
|
||||
},
|
||||
|
||||
// horizontal layout - current dashboard link styles
|
||||
'.horizontalLayoutWrapper &.linkCurrent': {
|
||||
padding: `0 ${euiTheme.size.s}`,
|
||||
'& .euiListGroupItem__text': {
|
||||
// add bottom border for current dashboard
|
||||
boxShadow: `${euiTheme.colors.textPrimary} 0 calc(-.5 * ${euiTheme.size.xs}) inset`,
|
||||
paddingInline: 0,
|
||||
},
|
||||
},
|
||||
|
||||
// dashboard not found error styles
|
||||
'&.dashboardLinkError': {
|
||||
'&.dashboardLinkError--noLabel .euiListGroupItem__text': {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
|
||||
'.dashboardLinkIcon': {
|
||||
marginRight: euiTheme.size.s,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiComboBoxOptionOption,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import { DashboardItem } from '../../types';
|
||||
import { DashboardLinkStrings } from './dashboard_link_strings';
|
||||
|
@ -100,7 +101,7 @@ export const DashboardLinkDestinationPicker = ({
|
|||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" className={contentClassName}>
|
||||
{dashboardId === parentDashboardId && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={false} className={'linksDashboardItem--current'}>
|
||||
<EuiBadge>{DashboardLinkStrings.getCurrentDashboardLabel()}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
@ -143,6 +144,25 @@ export const DashboardLinkDestinationPicker = ({
|
|||
}
|
||||
}}
|
||||
data-test-subj="links--linkEditor--dashboardLink--comboBox"
|
||||
inputPopoverProps={{ panelProps: { css: styles } }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = css({
|
||||
'.linksDashboardItem': {
|
||||
'.linksDashboardItem--current': {
|
||||
cursor: 'pointer !important',
|
||||
},
|
||||
// in order to ensure that the "Current" badge doesn't recieve an underline on hover, we have to set the
|
||||
// text-decoration to `none` for the entire list item and manually set the underline **only** on the text
|
||||
'&:hover': {
|
||||
textDecoration: 'none !important',
|
||||
},
|
||||
'.linksPanelEditorLinkText': {
|
||||
'&:hover': {
|
||||
textDecoration: 'underline !important',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
@import '../../mixins';
|
||||
|
||||
.linksPanelEditor {
|
||||
.linkEditor {
|
||||
@include euiFlyout;
|
||||
max-inline-size: $euiSizeXS * 125; // 4px * 125 = 500px
|
||||
|
||||
&.in {
|
||||
animation: euiFlyoutOpenAnimation $euiAnimSpeedNormal $euiAnimSlightResistance;
|
||||
}
|
||||
|
||||
&.out {
|
||||
animation: euiFlyoutCloseAnimation $euiAnimSpeedNormal $euiAnimSlightResistance;
|
||||
}
|
||||
|
||||
.linkEditorBackButton {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.linksDashboardItem {
|
||||
.euiBadge {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
// in order to ensure that the "Current" badge doesn't recieve an underline on hover, we have to set the
|
||||
// text-decoration to `none` for the entire list item and manually set the underline **only** on the text
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.linksPanelEditorLinkText {
|
||||
&:hover {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.linksPanelEditorLink {
|
||||
padding: $euiSizeXS $euiSizeS;
|
||||
color: $euiTextColor;
|
||||
|
||||
.linksPanelEditorLinkText {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&.linkError {
|
||||
border: 1px solid transparentize($euiColorWarningText, .7);
|
||||
|
||||
.linksPanelEditorLinkText {
|
||||
color: $euiColorWarningText;
|
||||
}
|
||||
|
||||
.linksPanelEditorLinkText--noLabel {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.links_hoverActions {
|
||||
background-color: $euiColorEmptyShade;
|
||||
position: absolute;
|
||||
right: $euiSizeL;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: visibility $euiAnimSpeedNormal, opacity $euiAnimSpeedNormal;
|
||||
}
|
||||
|
||||
&:hover, &:focus-within {
|
||||
.links_hoverActions {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.linksDroppableLinksArea {
|
||||
margin: 0 (-$euiSizeXS);
|
||||
}
|
|
@ -10,20 +10,13 @@
|
|||
import React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import LinksEditor from './links_editor';
|
||||
import { EuiThemeProvider } from '@elastic/eui';
|
||||
import LinksEditor, { LinksEditorProps } from './links_editor';
|
||||
import { LinksStrings } from '../links_strings';
|
||||
import { LINKS_VERTICAL_LAYOUT } from '../../../common/content_management';
|
||||
import { ResolvedLink } from '../../types';
|
||||
|
||||
describe('LinksEditor', () => {
|
||||
const defaultProps = {
|
||||
onSaveToLibrary: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
onAddToDashboard: jest.fn(),
|
||||
onClose: jest.fn(),
|
||||
isByReference: false,
|
||||
flyoutId: 'test-id',
|
||||
};
|
||||
|
||||
const someLinks: ResolvedLink[] = [
|
||||
{
|
||||
id: 'foo',
|
||||
|
@ -60,8 +53,24 @@ describe('LinksEditor', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const renderEditor = (overrides?: Partial<LinksEditorProps>) => {
|
||||
const defaultProps = {
|
||||
onSaveToLibrary: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
onAddToDashboard: jest.fn(),
|
||||
onClose: jest.fn(),
|
||||
isByReference: false,
|
||||
flyoutId: 'test-id',
|
||||
};
|
||||
return render(
|
||||
<EuiThemeProvider>
|
||||
<LinksEditor {...defaultProps} {...overrides} />
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
test('shows empty state with no links', async () => {
|
||||
render(<LinksEditor {...defaultProps} />);
|
||||
const onClose = jest.fn();
|
||||
renderEditor({ onClose });
|
||||
expect(screen.getByTestId('links--panelEditor--title')).toHaveTextContent(
|
||||
LinksStrings.editor.panelEditor.getCreateFlyoutTitle()
|
||||
);
|
||||
|
@ -69,12 +78,13 @@ describe('LinksEditor', () => {
|
|||
expect(screen.getByTestId('links--panelEditor--saveBtn')).toBeDisabled();
|
||||
|
||||
await userEvent.click(screen.getByTestId('links--panelEditor--closeBtn'));
|
||||
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('shows links in order', async () => {
|
||||
const expectedLinkIds = [...someLinks].sort((a, b) => a.order - b.order).map(({ id }) => id);
|
||||
render(<LinksEditor {...defaultProps} initialLinks={someLinks} />);
|
||||
renderEditor({ initialLinks: someLinks });
|
||||
|
||||
expect(screen.getByTestId('links--panelEditor--title')).toHaveTextContent(
|
||||
LinksStrings.editor.panelEditor.getEditFlyoutTitle()
|
||||
);
|
||||
|
@ -88,19 +98,23 @@ describe('LinksEditor', () => {
|
|||
|
||||
test('saving by reference panels calls onSaveToLibrary', async () => {
|
||||
const orderedLinks = [...someLinks].sort((a, b) => a.order - b.order);
|
||||
render(<LinksEditor {...defaultProps} initialLinks={someLinks} isByReference />);
|
||||
const onSaveToLibrary = jest.fn().mockImplementation(() => Promise.resolve());
|
||||
renderEditor({ initialLinks: someLinks, onSaveToLibrary, isByReference: true });
|
||||
|
||||
const saveButton = screen.getByTestId('links--panelEditor--saveBtn');
|
||||
await userEvent.click(saveButton);
|
||||
await waitFor(() => expect(defaultProps.onSaveToLibrary).toHaveBeenCalledTimes(1));
|
||||
expect(defaultProps.onSaveToLibrary).toHaveBeenCalledWith(orderedLinks, LINKS_VERTICAL_LAYOUT);
|
||||
await waitFor(() => expect(onSaveToLibrary).toHaveBeenCalledTimes(1));
|
||||
expect(onSaveToLibrary).toHaveBeenCalledWith(orderedLinks, LINKS_VERTICAL_LAYOUT);
|
||||
});
|
||||
|
||||
test('saving by value panel calls onAddToDashboard', async () => {
|
||||
const orderedLinks = [...someLinks].sort((a, b) => a.order - b.order);
|
||||
render(<LinksEditor {...defaultProps} initialLinks={someLinks} isByReference={false} />);
|
||||
const onAddToDashboard = jest.fn();
|
||||
renderEditor({ initialLinks: someLinks, onAddToDashboard, isByReference: false });
|
||||
|
||||
const saveButton = screen.getByTestId('links--panelEditor--saveBtn');
|
||||
await userEvent.click(saveButton);
|
||||
expect(defaultProps.onAddToDashboard).toHaveBeenCalledTimes(1);
|
||||
expect(defaultProps.onAddToDashboard).toHaveBeenCalledWith(orderedLinks, LINKS_VERTICAL_LAYOUT);
|
||||
expect(onAddToDashboard).toHaveBeenCalledTimes(1);
|
||||
expect(onAddToDashboard).toHaveBeenCalledWith(orderedLinks, LINKS_VERTICAL_LAYOUT);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,26 +29,25 @@ import {
|
|||
EuiFormRow,
|
||||
EuiSwitch,
|
||||
EuiTitle,
|
||||
UseEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { css, keyframes } from '@emotion/react';
|
||||
|
||||
import {
|
||||
LinksLayoutType,
|
||||
LINKS_HORIZONTAL_LAYOUT,
|
||||
LINKS_VERTICAL_LAYOUT,
|
||||
LinksLayoutType,
|
||||
} from '../../../common/content_management';
|
||||
import { focusMainFlyout } from '../../editor/links_editor_tools';
|
||||
import { openLinkEditorFlyout } from '../../editor/open_link_editor_flyout';
|
||||
import { getOrderedLinkList } from '../../lib/resolve_links';
|
||||
import { coreServices } from '../../services/kibana_services';
|
||||
import { ResolvedLink } from '../../types';
|
||||
import { LinksStrings } from '../links_strings';
|
||||
import { TooltipWrapper } from '../tooltip_wrapper';
|
||||
import { LinksEditorEmptyPrompt } from './links_editor_empty_prompt';
|
||||
import { LinksEditorSingleLink } from './links_editor_single_link';
|
||||
|
||||
import { TooltipWrapper } from '../tooltip_wrapper';
|
||||
|
||||
import './links_editor.scss';
|
||||
import { ResolvedLink } from '../../types';
|
||||
import { getOrderedLinkList } from '../../lib/resolve_links';
|
||||
|
||||
const layoutOptions: EuiButtonGroupOptionProps[] = [
|
||||
{
|
||||
id: LINKS_VERTICAL_LAYOUT,
|
||||
|
@ -62,6 +61,17 @@ const layoutOptions: EuiButtonGroupOptionProps[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export interface LinksEditorProps {
|
||||
onSaveToLibrary: (newLinks: ResolvedLink[], newLayout: LinksLayoutType) => Promise<void>;
|
||||
onAddToDashboard: (newLinks: ResolvedLink[], newLayout: LinksLayoutType) => void;
|
||||
onClose: () => void;
|
||||
initialLinks?: ResolvedLink[];
|
||||
initialLayout?: LinksLayoutType;
|
||||
parentDashboardId?: string;
|
||||
isByReference: boolean;
|
||||
flyoutId: string; // used to manage the focus of this flyout after individual link editor flyout is closed
|
||||
}
|
||||
|
||||
const LinksEditor = ({
|
||||
onSaveToLibrary,
|
||||
onAddToDashboard,
|
||||
|
@ -71,16 +81,7 @@ const LinksEditor = ({
|
|||
parentDashboardId,
|
||||
isByReference,
|
||||
flyoutId,
|
||||
}: {
|
||||
onSaveToLibrary: (newLinks: ResolvedLink[], newLayout: LinksLayoutType) => Promise<void>;
|
||||
onAddToDashboard: (newLinks: ResolvedLink[], newLayout: LinksLayoutType) => void;
|
||||
onClose: () => void;
|
||||
initialLinks?: ResolvedLink[];
|
||||
initialLayout?: LinksLayoutType;
|
||||
parentDashboardId?: string;
|
||||
isByReference: boolean;
|
||||
flyoutId: string; // used to manage the focus of this flyout after individual link editor flyout is closed
|
||||
}) => {
|
||||
}: LinksEditorProps) => {
|
||||
const toasts = coreServices.notifications.toasts;
|
||||
const isMounted = useMountedState();
|
||||
const editLinkFlyoutRef = useRef<HTMLDivElement>(null);
|
||||
|
@ -163,7 +164,7 @@ const LinksEditor = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div ref={editLinkFlyoutRef} />
|
||||
<div css={styles.flyoutStyles} ref={editLinkFlyoutRef} />
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -177,12 +178,7 @@ const LinksEditor = ({
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody
|
||||
// EUI TODO: We need to set transform to 'none' to avoid drag/drop issues in the flyout caused by the
|
||||
// `transform: translateZ(0)` workaround for the mask image bug in Chromium.
|
||||
// https://github.com/elastic/eui/pull/7855.
|
||||
css={{ '.euiFlyoutBody__overflow': { transform: 'none' } }}
|
||||
>
|
||||
<EuiFlyoutBody css={styles.bodyStyles}>
|
||||
<EuiForm fullWidth>
|
||||
<EuiFormRow label={LinksStrings.editor.panelEditor.getLayoutSettingsTitle()}>
|
||||
<EuiButtonGroup
|
||||
|
@ -205,7 +201,7 @@ const LinksEditor = ({
|
|||
<>
|
||||
<EuiDragDropContext onDragEnd={onDragEnd}>
|
||||
<EuiDroppable
|
||||
className="linksDroppableLinksArea"
|
||||
css={styles.droppableStyles}
|
||||
droppableId="linksDroppableLinksArea"
|
||||
data-test-subj="links--panelEditor--linksAreaDroppable"
|
||||
>
|
||||
|
@ -322,3 +318,61 @@ const LinksEditor = ({
|
|||
// required for dynamic import using React.lazy()
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default LinksEditor;
|
||||
|
||||
const styles = {
|
||||
droppableStyles: ({ euiTheme }: UseEuiTheme) => css({ margin: `0 -${euiTheme.size.xs}` }),
|
||||
bodyStyles: css({
|
||||
// EUI TODO: We need to set transform to 'none' to avoid drag/drop issues in the flyout caused by the
|
||||
// `transform: translateZ(0)` workaround for the mask image bug in Chromium.
|
||||
// https://github.com/elastic/eui/pull/7855.
|
||||
'& .euiFlyoutBody__overflow': {
|
||||
transform: 'none',
|
||||
},
|
||||
}),
|
||||
flyoutStyles: ({ euiTheme }: UseEuiTheme) => {
|
||||
const euiFlyoutOpenAnimation = keyframes`
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
`;
|
||||
|
||||
const euiFlyoutCloseAnimation = keyframes`
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}`;
|
||||
|
||||
return css({
|
||||
'.linkEditor': {
|
||||
maxInlineSize: `calc(${euiTheme.size.xs} * 125)`,
|
||||
height: 'calc(100vh - var(--euiFixedHeadersOffset, 0))',
|
||||
position: 'fixed',
|
||||
display: 'flex',
|
||||
inlineSize: '50vw',
|
||||
zIndex: euiTheme.levels.flyout,
|
||||
alignItems: 'stretch',
|
||||
flexDirection: 'column',
|
||||
borderLeft: euiTheme.border.thin,
|
||||
background: euiTheme.colors.backgroundBasePlain,
|
||||
minWidth: `calc((${euiTheme.size.xl} * 13) + ${euiTheme.size.s})`, // 424px
|
||||
'&.in': {
|
||||
animation: `${euiFlyoutOpenAnimation} ${euiTheme.animation.normal} ${euiTheme.animation.resistance}`,
|
||||
},
|
||||
'&.out': {
|
||||
animation: `${euiFlyoutCloseAnimation} ${euiTheme.animation.normal} ${euiTheme.animation.resistance}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
|
@ -19,7 +18,10 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiButtonIcon,
|
||||
DraggableProvidedDragHandleProps,
|
||||
UseEuiTheme,
|
||||
transparentize,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import { LinkInfo } from './constants';
|
||||
import { LinksStrings } from '../links_strings';
|
||||
|
@ -53,11 +55,7 @@ export const LinksEditorSingleLink = ({
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem
|
||||
className={classNames('linksPanelEditorLinkText', {
|
||||
'linksPanelEditorLinkText--noLabel': !link.label,
|
||||
})}
|
||||
>
|
||||
<EuiFlexItem className={'linksPanelEditorLinkText'}>
|
||||
<EuiText size="s" color={'default'} className="eui-textTruncate">
|
||||
{link.label || link.title}
|
||||
</EuiText>
|
||||
|
@ -85,10 +83,11 @@ export const LinksEditorSingleLink = ({
|
|||
return (
|
||||
<EuiPanel
|
||||
hasBorder
|
||||
css={styles}
|
||||
hasShadow={false}
|
||||
color={link.error ? 'warning' : 'plain'}
|
||||
className={`linksPanelEditorLink ${link.error ? 'linkError' : ''}`}
|
||||
data-test-subj={`panelEditorLink''}`}
|
||||
data-test-subj={`panelEditorLink`}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" responsive={false} wrap={false} alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -135,3 +134,28 @@ export const LinksEditorSingleLink = ({
|
|||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = ({ euiTheme }: UseEuiTheme) =>
|
||||
css({
|
||||
padding: `${euiTheme.size.xs} ${euiTheme.size.s}`,
|
||||
color: euiTheme.colors.textParagraph,
|
||||
'.linksPanelEditorLinkText': {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
'&.linkError': {
|
||||
border: `1px solid ${transparentize(euiTheme.colors.textWarning, 0.3)}`,
|
||||
color: euiTheme.colors.textWarning,
|
||||
},
|
||||
'& .links_hoverActions': {
|
||||
position: 'absolute',
|
||||
right: euiTheme.size.l,
|
||||
opacity: 0,
|
||||
visibility: 'hidden',
|
||||
transition: `visibility ${euiTheme.animation.normal}, opacity ${euiTheme.animation.normal}`,
|
||||
},
|
||||
'&:hover .links_hoverActions, &:focus-within .links_hoverActions ': {
|
||||
opacity: 1,
|
||||
visibility: 'visible',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
.linksComponent {
|
||||
|
||||
.linksPanelLink {
|
||||
max-width: fit-content; // added this so that the error tooltip shows up **right beside** the link label
|
||||
|
||||
&.dashboardLinkError {
|
||||
&.dashboardLinkError--noLabel .euiListGroupItem__button {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.dashboardLinkIcon {
|
||||
margin-right: $euiSizeS;
|
||||
}
|
||||
}
|
||||
|
||||
&.linkCurrent {
|
||||
border-radius: 0;
|
||||
.euiListGroupItem__text {
|
||||
cursor: default;
|
||||
color: $euiColorPrimary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.verticalLayoutWrapper {
|
||||
gap: $euiSizeXS;
|
||||
.linksPanelLink {
|
||||
&.linkCurrent {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: .5 * $euiSizeXS;
|
||||
height: 75%;
|
||||
background-color: $euiColorPrimary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.horizontalLayoutWrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
.linksPanelLink {
|
||||
&.linkCurrent {
|
||||
padding: 0 $euiSizeS;
|
||||
|
||||
.euiListGroupItem__text {
|
||||
box-shadow: $euiColorPrimary 0 (-.5 * $euiSizeXS) inset;
|
||||
padding-inline: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -142,7 +142,6 @@ export async function openEditorFlyout({
|
|||
ownFocus: true,
|
||||
onClose: onCancel,
|
||||
outsideClickCloses: false,
|
||||
className: 'linksPanelEditor',
|
||||
'data-test-subj': 'links--panelEditor--flyout',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ import React from 'react';
|
|||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks';
|
||||
import { setStubKibanaServices } from '@kbn/presentation-panel-plugin/public/mocks';
|
||||
import { EuiThemeProvider } from '@elastic/eui';
|
||||
import { getLinksEmbeddableFactory } from './links_embeddable';
|
||||
import { Link } from '../../common/content_management';
|
||||
import { CONTENT_ID } from '../../common';
|
||||
|
@ -139,8 +140,27 @@ jest.mock('../content_management', () => {
|
|||
};
|
||||
});
|
||||
|
||||
const renderEmbeddable = (
|
||||
parent: LinksParentApi,
|
||||
overrides?: {
|
||||
onApiAvailable: (api: LinksApi) => void;
|
||||
}
|
||||
) => {
|
||||
return render(
|
||||
<EuiThemeProvider>
|
||||
<ReactEmbeddableRenderer<LinksSerializedState, LinksRuntimeState, LinksApi>
|
||||
type={CONTENT_ID}
|
||||
onApiAvailable={jest.fn()}
|
||||
getParentApi={jest.fn().mockReturnValue(parent)}
|
||||
{...overrides}
|
||||
/>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('getLinksEmbeddableFactory', () => {
|
||||
const factory = getLinksEmbeddableFactory();
|
||||
|
||||
beforeAll(() => {
|
||||
const embeddable = embeddablePluginMock.createSetupContract();
|
||||
embeddable.registerReactEmbeddableFactory(CONTENT_ID, async () => {
|
||||
|
@ -185,30 +205,17 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
});
|
||||
|
||||
test('component renders', async () => {
|
||||
render(
|
||||
<ReactEmbeddableRenderer<LinksSerializedState, LinksApi>
|
||||
type={CONTENT_ID}
|
||||
getParentApi={() => parent}
|
||||
/>
|
||||
);
|
||||
|
||||
renderEmbeddable(parent);
|
||||
expect(await screen.findByTestId('links--component')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('api methods', async () => {
|
||||
const onApiAvailable = jest.fn() as jest.MockedFunction<(api: LinksApi) => void>;
|
||||
|
||||
render(
|
||||
<ReactEmbeddableRenderer<LinksSerializedState, LinksRuntimeState, LinksApi>
|
||||
type={CONTENT_ID}
|
||||
onApiAvailable={onApiAvailable}
|
||||
getParentApi={() => parent}
|
||||
/>
|
||||
);
|
||||
renderEmbeddable(parent, { onApiAvailable });
|
||||
|
||||
await waitFor(async () => {
|
||||
const api = onApiAvailable.mock.calls[0][0];
|
||||
expect(await api.serializeState()).toEqual({
|
||||
expect(api.serializeState()).toEqual({
|
||||
rawState: {
|
||||
savedObjectId: '123',
|
||||
title: 'my links',
|
||||
|
@ -225,18 +232,11 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
|
||||
test('unlink from library', async () => {
|
||||
const onApiAvailable = jest.fn() as jest.MockedFunction<(api: LinksApi) => void>;
|
||||
|
||||
render(
|
||||
<ReactEmbeddableRenderer<LinksSerializedState, LinksRuntimeState, LinksApi>
|
||||
type={CONTENT_ID}
|
||||
onApiAvailable={onApiAvailable}
|
||||
getParentApi={() => parent}
|
||||
/>
|
||||
);
|
||||
renderEmbeddable(parent, { onApiAvailable });
|
||||
|
||||
await waitFor(async () => {
|
||||
const api = onApiAvailable.mock.calls[0][0];
|
||||
expect(await api.getSerializedStateByValue()).toEqual({
|
||||
expect(api.getSerializedStateByValue()).toEqual({
|
||||
rawState: {
|
||||
title: 'my links',
|
||||
description: 'just a few links',
|
||||
|
@ -291,30 +291,18 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
});
|
||||
|
||||
test('component renders', async () => {
|
||||
render(
|
||||
<ReactEmbeddableRenderer<LinksSerializedState, LinksApi>
|
||||
type={CONTENT_ID}
|
||||
getParentApi={() => parent}
|
||||
/>
|
||||
);
|
||||
renderEmbeddable(parent);
|
||||
|
||||
expect(await screen.findByTestId('links--component')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('api methods', async () => {
|
||||
const onApiAvailable = jest.fn() as jest.MockedFunction<(api: LinksApi) => void>;
|
||||
|
||||
render(
|
||||
<ReactEmbeddableRenderer<LinksSerializedState, LinksRuntimeState, LinksApi>
|
||||
type={CONTENT_ID}
|
||||
onApiAvailable={onApiAvailable}
|
||||
getParentApi={() => parent}
|
||||
/>
|
||||
);
|
||||
renderEmbeddable(parent, { onApiAvailable });
|
||||
|
||||
await waitFor(async () => {
|
||||
const api = onApiAvailable.mock.calls[0][0];
|
||||
expect(await api.serializeState()).toEqual({
|
||||
expect(api.serializeState()).toEqual({
|
||||
rawState: {
|
||||
title: 'my links',
|
||||
description: 'just a few links',
|
||||
|
@ -332,14 +320,7 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
});
|
||||
test('save to library', async () => {
|
||||
const onApiAvailable = jest.fn() as jest.MockedFunction<(api: LinksApi) => void>;
|
||||
|
||||
render(
|
||||
<ReactEmbeddableRenderer<LinksSerializedState, LinksRuntimeState, LinksApi>
|
||||
type={CONTENT_ID}
|
||||
onApiAvailable={onApiAvailable}
|
||||
getParentApi={() => parent}
|
||||
/>
|
||||
);
|
||||
renderEmbeddable(parent, { onApiAvailable });
|
||||
|
||||
await waitFor(async () => {
|
||||
const api = onApiAvailable.mock.calls[0][0];
|
||||
|
@ -353,7 +334,7 @@ describe('getLinksEmbeddableFactory', () => {
|
|||
options: { references },
|
||||
});
|
||||
expect(newId).toBe('333');
|
||||
expect(await api.getSerializedStateByReference(newId)).toEqual({
|
||||
expect(api.getSerializedStateByReference(newId)).toEqual({
|
||||
rawState: {
|
||||
savedObjectId: '333',
|
||||
title: 'my links',
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import React, { createContext, useMemo } from 'react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { EuiListGroup, EuiPanel } from '@elastic/eui';
|
||||
import { EuiListGroup, EuiPanel, UseEuiTheme } from '@elastic/eui';
|
||||
|
||||
import { PanelIncompatibleError, ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
|
||||
import {
|
||||
|
@ -19,6 +19,7 @@ import {
|
|||
SerializedPanelState,
|
||||
useBatchedOptionalPublishingSubjects,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import {
|
||||
CONTENT_ID,
|
||||
|
@ -41,7 +42,6 @@ import {
|
|||
import { DISPLAY_NAME } from '../../common';
|
||||
import { injectReferences } from '../../common/persistable_state';
|
||||
|
||||
import '../components/links_component.scss';
|
||||
import { checkForDuplicateTitle, linksClient } from '../content_management';
|
||||
import { resolveLinks } from '../lib/resolve_links';
|
||||
import {
|
||||
|
@ -248,9 +248,7 @@ export const getLinksEmbeddableFactory = () => {
|
|||
}, [links, layout]);
|
||||
return (
|
||||
<EuiPanel
|
||||
className={`linksComponent ${
|
||||
layout === LINKS_HORIZONTAL_LAYOUT ? 'eui-xScroll' : 'eui-yScroll'
|
||||
}`}
|
||||
className={layout === LINKS_HORIZONTAL_LAYOUT ? 'eui-xScroll' : 'eui-yScroll'}
|
||||
paddingSize="xs"
|
||||
data-shared-item
|
||||
data-rendering-count={1}
|
||||
|
@ -259,6 +257,7 @@ export const getLinksEmbeddableFactory = () => {
|
|||
>
|
||||
<EuiListGroup
|
||||
maxWidth={false}
|
||||
css={styles}
|
||||
className={`${layout ?? LINKS_VERTICAL_LAYOUT}LayoutWrapper`}
|
||||
data-test-subj="links--component--listGroup"
|
||||
>
|
||||
|
@ -275,3 +274,20 @@ export const getLinksEmbeddableFactory = () => {
|
|||
};
|
||||
return linksEmbeddableFactory;
|
||||
};
|
||||
|
||||
const styles = ({ euiTheme }: UseEuiTheme) =>
|
||||
css({
|
||||
'.linksPanelLink': {
|
||||
maxWidth: 'fit-content', // ensures that the error tooltip shows up **right beside** the link label
|
||||
},
|
||||
'&.verticalLayoutWrapper': {
|
||||
gap: euiTheme.size.xs,
|
||||
},
|
||||
'&.horizontalLayoutWrapper': {
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -3,7 +3,14 @@
|
|||
"compilerOptions": {
|
||||
"outDir": "target/types"
|
||||
},
|
||||
"include": ["*.ts", "public/**/*", "common/**/*", "server/**/*", "public/**/*.json"],
|
||||
"include": [
|
||||
"*.ts",
|
||||
"public/**/*",
|
||||
"common/**/*",
|
||||
"server/**/*",
|
||||
"public/**/*.json",
|
||||
"../../../../../typings/emotion.d.ts"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
"@kbn/i18n",
|
||||
|
@ -35,7 +42,7 @@
|
|||
"@kbn/presentation-panel-plugin",
|
||||
"@kbn/embeddable-enhanced-plugin",
|
||||
"@kbn/share-plugin",
|
||||
"@kbn/es-query",
|
||||
"@kbn/es-query"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue