Skip to content

Commit fe754f0

Browse files
committed
Merge branch 'navigate-to-lens-tsvb-metric' into navigate-to-lens-agg-based-vis-lib-tests
2 parents ebf69ef + f5d40e7 commit fe754f0

File tree

79 files changed

+1709
-421
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+1709
-421
lines changed

.buildkite/scripts/steps/test/jest_parallel.sh

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ exitCode=0
1010
results=()
1111

1212
if [[ "$1" == 'jest.config.js' ]]; then
13-
# run unit tests in parallel
14-
parallelism="-w2"
13+
# we used to run jest tests in parallel but started to see a lot of flakiness in libraries like react-dom/test-utils:
14+
# https://github.com/elastic/kibana/issues/141477
15+
# parallelism="-w2"
16+
parallelism="--runInBand"
1517
TEST_TYPE="unit"
1618
else
1719
# run integration tests in-band
@@ -26,11 +28,17 @@ configs=$(jq -r 'getpath([env.TEST_TYPE]) | .groups[env.JOB | tonumber].names |
2628

2729
while read -r config; do
2830
echo "--- $ node scripts/jest --config $config"
31+
32+
cmd="NODE_OPTIONS=\"--max-old-space-size=14336\" node ./scripts/jest --config=\"$config\" $parallelism --coverage=false --passWithNoTests"
33+
echo "actual full command is:"
34+
echo "$cmd"
35+
echo ""
36+
2937
start=$(date +%s)
3038

3139
# prevent non-zero exit code from breaking the loop
3240
set +e;
33-
NODE_OPTIONS="--max-old-space-size=14336" node ./scripts/jest --config="$config" "$parallelism" --coverage=false --passWithNoTests
41+
eval "$cmd"
3442
lastCode=$?
3543
set -e;
3644

packages/content-management/table_list/src/__jest__/tests.helpers.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import React from 'react';
99
import type { ComponentType } from 'react';
1010
import { from } from 'rxjs';
1111

12+
import { TagList } from '../mocks';
1213
import { TableListViewProvider, Services } from '../services';
1314

1415
export const getMockServices = (overrides?: Partial<Services>) => {
@@ -18,6 +19,8 @@ export const getMockServices = (overrides?: Partial<Services>) => {
1819
notifyError: () => undefined,
1920
currentAppId$: from('mockedApp'),
2021
navigateToUrl: () => undefined,
22+
TagList,
23+
itemHasTags: () => true,
2124
...overrides,
2225
};
2326

packages/content-management/table_list/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export { Table } from './table';
1010
export { UpdatedAtField } from './updated_at_field';
1111
export { ConfirmDeleteModal } from './confirm_delete_modal';
1212
export { ListingLimitWarning } from './listing_limit_warning';
13+
export { ItemDetails } from './item_details';
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
import React, { useCallback, useMemo } from 'react';
10+
import { EuiText, EuiLink, EuiTitle, EuiSpacer } from '@elastic/eui';
11+
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
12+
13+
import { useServices } from '../services';
14+
import type { UserContentCommonSchema, Props as TableListViewProps } from '../table_list_view';
15+
16+
type InheritedProps<T extends UserContentCommonSchema> = Pick<
17+
TableListViewProps<T>,
18+
'onClickTitle' | 'getDetailViewLink' | 'id'
19+
>;
20+
interface Props<T extends UserContentCommonSchema> extends InheritedProps<T> {
21+
item: T;
22+
searchTerm?: string;
23+
}
24+
25+
/**
26+
* Copied from https://stackoverflow.com/a/9310752
27+
*/
28+
// const escapeRegExp = (text: string) => {
29+
// return text.replace(/[-\[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
30+
// };
31+
32+
export function ItemDetails<T extends UserContentCommonSchema>({
33+
id,
34+
item,
35+
searchTerm = '',
36+
getDetailViewLink,
37+
onClickTitle,
38+
}: Props<T>) {
39+
const {
40+
references,
41+
attributes: { title, description },
42+
} = item;
43+
const { navigateToUrl, currentAppId$, TagList, itemHasTags } = useServices();
44+
45+
const redirectAppLinksCoreStart = useMemo(
46+
() => ({
47+
application: {
48+
navigateToUrl,
49+
currentAppId$,
50+
},
51+
}),
52+
[currentAppId$, navigateToUrl]
53+
);
54+
55+
const onClickTitleHandler = useMemo(() => {
56+
if (!onClickTitle) {
57+
return undefined;
58+
}
59+
60+
return ((e) => {
61+
e.preventDefault();
62+
onClickTitle(item);
63+
}) as React.MouseEventHandler<HTMLAnchorElement>;
64+
}, [item, onClickTitle]);
65+
66+
const renderTitle = useCallback(() => {
67+
const href = getDetailViewLink ? getDetailViewLink(item) : undefined;
68+
69+
if (!href && !onClickTitle) {
70+
// This item is not clickable
71+
return <span>{title}</span>;
72+
}
73+
74+
return (
75+
<RedirectAppLinks coreStart={redirectAppLinksCoreStart}>
76+
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
77+
<EuiLink
78+
href={getDetailViewLink ? getDetailViewLink(item) : undefined}
79+
onClick={onClickTitleHandler}
80+
data-test-subj={`${id}ListingTitleLink-${item.attributes.title.split(' ').join('-')}`}
81+
>
82+
{title}
83+
</EuiLink>
84+
</RedirectAppLinks>
85+
);
86+
}, [
87+
getDetailViewLink,
88+
id,
89+
item,
90+
onClickTitle,
91+
onClickTitleHandler,
92+
redirectAppLinksCoreStart,
93+
title,
94+
]);
95+
96+
const hasTags = itemHasTags(references);
97+
98+
return (
99+
<div>
100+
<EuiTitle size="xs">{renderTitle()}</EuiTitle>
101+
{Boolean(description) && (
102+
<EuiText size="s">
103+
<p>{description!}</p>
104+
</EuiText>
105+
)}
106+
{hasTags && (
107+
<>
108+
<EuiSpacer size="s" />
109+
<TagList references={references} />
110+
</>
111+
)}
112+
</div>
113+
);
114+
}

packages/content-management/table_list/src/mocks.ts renamed to packages/content-management/table_list/src/mocks.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
* in compliance with, at your election, the Elastic License 2.0 or the Server
66
* Side Public License, v 1.
77
*/
8+
import React from 'react';
89
import { from } from 'rxjs';
10+
import { EuiBadgeGroup, EuiBadge } from '@elastic/eui';
911

1012
import { Services } from './services';
1113

@@ -15,6 +17,43 @@ import { Services } from './services';
1517
export type Params = Record<keyof ReturnType<typeof getStoryArgTypes>, any>;
1618
type ActionFn = (name: string) => any;
1719

20+
const tags = [
21+
{
22+
name: 'elastic',
23+
color: '#8dc4de',
24+
description: 'elastic tag',
25+
},
26+
{
27+
name: 'cloud',
28+
color: '#f5ed14',
29+
description: 'cloud tag',
30+
},
31+
];
32+
33+
export const TagList: Services['TagList'] = ({ onClick }) => {
34+
return (
35+
<EuiBadgeGroup>
36+
{tags.map((tag) => (
37+
<EuiBadge
38+
key={tag.name}
39+
onClick={() => {
40+
if (onClick) {
41+
onClick(tag);
42+
}
43+
}}
44+
onClickAriaLabel="tag button"
45+
iconOnClick={() => undefined}
46+
iconOnClickAriaLabel=""
47+
color={tag.color}
48+
title={tag.description}
49+
>
50+
{tag.name}
51+
</EuiBadge>
52+
))}
53+
</EuiBadgeGroup>
54+
);
55+
};
56+
1857
/**
1958
* Returns Storybook-compatible service abstractions for the `NoDataCard` Provider.
2059
*/
@@ -27,6 +66,8 @@ export const getStoryServices = (params: Params, action: ActionFn = () => {}) =>
2766
},
2867
currentAppId$: from('mockedApp'),
2968
navigateToUrl: () => undefined,
69+
TagList,
70+
itemHasTags: () => true,
3071
...params,
3172
};
3273

packages/content-management/table_list/src/reducer.tsx

Lines changed: 16 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,54 +5,12 @@
55
* in compliance with, at your election, the Elastic License 2.0 or the Server
66
* Side Public License, v 1.
77
*/
8-
import React from 'react';
98
import { sortBy } from 'lodash';
10-
import { i18n } from '@kbn/i18n';
119

12-
import { UpdatedAtField } from './components';
1310
import type { State, UserContentCommonSchema } from './table_list_view';
1411
import type { Action } from './actions';
15-
import type { Services } from './services';
1612

17-
interface Dependencies {
18-
DateFormatterComp: Services['DateFormatterComp'];
19-
}
20-
21-
function onInitialItemsFetch<T extends UserContentCommonSchema>(
22-
items: T[],
23-
{ DateFormatterComp }: Dependencies
24-
) {
25-
// We check if the saved object have the "updatedAt" metadata
26-
// to render or not that column in the table
27-
const hasUpdatedAtMetadata = Boolean(items.find((item) => Boolean(item.updatedAt)));
28-
29-
if (hasUpdatedAtMetadata) {
30-
// Add "Last update" column and sort by that column initially
31-
return {
32-
tableSort: {
33-
field: 'updatedAt' as keyof T,
34-
direction: 'desc' as const,
35-
},
36-
tableColumns: [
37-
{
38-
field: 'updatedAt',
39-
name: i18n.translate('contentManagement.tableList.lastUpdatedColumnTitle', {
40-
defaultMessage: 'Last updated',
41-
}),
42-
render: (field: string, record: { updatedAt?: string }) => (
43-
<UpdatedAtField dateTime={record.updatedAt} DateFormatterComp={DateFormatterComp} />
44-
),
45-
sortable: true,
46-
width: '150px',
47-
},
48-
],
49-
};
50-
}
51-
52-
return {};
53-
}
54-
55-
export function getReducer<T extends UserContentCommonSchema>({ DateFormatterComp }: Dependencies) {
13+
export function getReducer<T extends UserContentCommonSchema>() {
5614
return (state: State<T>, action: Action<T>): State<T> => {
5715
switch (action.type) {
5816
case 'onFetchItems': {
@@ -63,21 +21,28 @@ export function getReducer<T extends UserContentCommonSchema>({ DateFormatterCom
6321
}
6422
case 'onFetchItemsSuccess': {
6523
const items = action.data.response.hits;
66-
// We only get the state on the initial fetch of items
67-
// After that we don't want to reset the columns or change the sort after fetching
68-
const { tableColumns, tableSort } = state.hasInitialFetchReturned
69-
? { tableColumns: undefined, tableSort: undefined }
70-
: onInitialItemsFetch(items, { DateFormatterComp });
24+
let tableSort;
25+
let hasUpdatedAtMetadata = state.hasUpdatedAtMetadata;
26+
27+
if (!state.hasInitialFetchReturned) {
28+
// We only get the state on the initial fetch of items
29+
// After that we don't want to reset the columns or change the sort after fetching
30+
hasUpdatedAtMetadata = Boolean(items.find((item) => Boolean(item.updatedAt)));
31+
if (hasUpdatedAtMetadata) {
32+
tableSort = {
33+
field: 'updatedAt' as keyof T,
34+
direction: 'desc' as const,
35+
};
36+
}
37+
}
7138

7239
return {
7340
...state,
7441
hasInitialFetchReturned: true,
7542
isFetchingItems: false,
7643
items: !state.searchQuery ? sortBy<T>(items, 'title') : items,
7744
totalItems: action.data.response.total,
78-
tableColumns: tableColumns
79-
? [...state.tableColumns, ...tableColumns]
80-
: state.tableColumns,
45+
hasUpdatedAtMetadata,
8146
tableSort: tableSort ?? state.tableSort,
8247
pagination: {
8348
...state.pagination,

0 commit comments

Comments
 (0)