+ ).inMemory
+ ).toMatchInlineSnapshot(`
+ Object {
+ "level": "sorting",
+ }
+ `);
+ });
+ });
});
diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx
index 34ddd0c995666..69748b449b05c 100644
--- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx
+++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx
@@ -362,13 +362,18 @@ export const DiscoverGrid = ({
*/
const sortingColumns = useMemo(() => sort.map(([id, direction]) => ({ id, direction })), [sort]);
+ const [inmemorySortingColumns, setInmemorySortingColumns] = useState([]);
const onTableSort = useCallback(
(sortingColumnsData) => {
- if (isSortEnabled && onSort) {
- onSort(sortingColumnsData.map(({ id, direction }: SortObj) => [id, direction]));
+ if (isSortEnabled) {
+ if (isPlainRecord) {
+ setInmemorySortingColumns(sortingColumnsData);
+ } else if (onSort) {
+ onSort(sortingColumnsData.map(({ id, direction }: SortObj) => [id, direction]));
+ }
}
},
- [onSort, isSortEnabled]
+ [onSort, isSortEnabled, isPlainRecord, setInmemorySortingColumns]
);
const showMultiFields = services.uiSettings.get(SHOW_MULTIFIELDS);
@@ -437,6 +442,7 @@ export const DiscoverGrid = ({
showTimeCol,
defaultColumns,
isSortEnabled,
+ isPlainRecord,
services: {
uiSettings,
toastNotifications,
@@ -455,6 +461,7 @@ export const DiscoverGrid = ({
settings,
defaultColumns,
isSortEnabled,
+ isPlainRecord,
uiSettings,
toastNotifications,
dataViewFieldEditor,
@@ -479,10 +486,13 @@ export const DiscoverGrid = ({
);
const sorting = useMemo(() => {
if (isSortEnabled) {
- return { columns: sortingColumns, onSort: onTableSort };
+ return {
+ columns: isPlainRecord ? inmemorySortingColumns : sortingColumns,
+ onSort: onTableSort,
+ };
}
return { columns: sortingColumns, onSort: () => {} };
- }, [sortingColumns, onTableSort, isSortEnabled]);
+ }, [isSortEnabled, sortingColumns, isPlainRecord, inmemorySortingColumns, onTableSort]);
const canSetExpandedDoc = Boolean(setExpandedDoc && DocumentView);
@@ -619,6 +629,7 @@ export const DiscoverGrid = ({
sorting={sorting as EuiDataGridSorting}
toolbarVisibility={toolbarVisibility}
rowHeightsOptions={rowHeightsOptions}
+ inMemory={isPlainRecord ? { level: 'sorting' } : undefined}
gridStyle={GRID_STYLE}
/>
diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_columns.test.tsx
index fd7122fccbd95..c4c68bf0132d0 100644
--- a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.test.tsx
+++ b/src/plugins/discover/public/components/discover_grid/discover_grid_columns.test.tsx
@@ -21,6 +21,7 @@ describe('Discover grid columns', function () {
showTimeCol: false,
defaultColumns: false,
isSortEnabled: true,
+ isPlainRecord: false,
valueToStringConverter: discoverGridContextMock.valueToStringConverter,
rowsCount: 100,
services: {
@@ -140,6 +141,7 @@ describe('Discover grid columns', function () {
showTimeCol: false,
defaultColumns: true,
isSortEnabled: true,
+ isPlainRecord: false,
valueToStringConverter: discoverGridContextMock.valueToStringConverter,
rowsCount: 100,
services: {
@@ -253,6 +255,7 @@ describe('Discover grid columns', function () {
showTimeCol: true,
defaultColumns: false,
isSortEnabled: true,
+ isPlainRecord: false,
valueToStringConverter: discoverGridContextMock.valueToStringConverter,
rowsCount: 100,
services: {
@@ -429,4 +432,190 @@ describe('Discover grid columns', function () {
]
`);
});
+
+ it('returns eui grid with inmemory sorting', async () => {
+ const actual = getEuiGridColumns({
+ columns: ['extension', 'message'],
+ settings: {},
+ dataView: dataViewWithTimefieldMock,
+ showTimeCol: true,
+ defaultColumns: false,
+ isSortEnabled: true,
+ isPlainRecord: true,
+ valueToStringConverter: discoverGridContextMock.valueToStringConverter,
+ rowsCount: 100,
+ services: {
+ uiSettings: discoverServiceMock.uiSettings,
+ toastNotifications: discoverServiceMock.toastNotifications,
+ },
+ hasEditDataViewPermission: () =>
+ discoverServiceMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
+ onFilter: () => {},
+ });
+ expect(actual).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "actions": Object {
+ "additional": Array [
+ Object {
+ "data-test-subj": "gridCopyColumnNameToClipBoardButton",
+ "iconProps": Object {
+ "size": "m",
+ },
+ "iconType": "copyClipboard",
+ "label": ,
+ "onClick": [Function],
+ "size": "xs",
+ },
+ Object {
+ "data-test-subj": "gridCopyColumnValuesToClipBoardButton",
+ "iconProps": Object {
+ "size": "m",
+ },
+ "iconType": "copyClipboard",
+ "label": ,
+ "onClick": [Function],
+ "size": "xs",
+ },
+ ],
+ "showHide": false,
+ "showMoveLeft": true,
+ "showMoveRight": true,
+ },
+ "cellActions": Array [
+ [Function],
+ [Function],
+ [Function],
+ ],
+ "display":
+
+
+ timestamp
+
+
+
+
+
,
+ "displayAsText": "timestamp",
+ "id": "timestamp",
+ "initialWidth": 210,
+ "isSortable": true,
+ "schema": "datetime",
+ },
+ Object {
+ "actions": Object {
+ "additional": Array [
+ Object {
+ "data-test-subj": "gridCopyColumnNameToClipBoardButton",
+ "iconProps": Object {
+ "size": "m",
+ },
+ "iconType": "copyClipboard",
+ "label": ,
+ "onClick": [Function],
+ "size": "xs",
+ },
+ Object {
+ "data-test-subj": "gridCopyColumnValuesToClipBoardButton",
+ "iconProps": Object {
+ "size": "m",
+ },
+ "iconType": "copyClipboard",
+ "label": ,
+ "onClick": [Function],
+ "size": "xs",
+ },
+ ],
+ "showHide": Object {
+ "iconType": "cross",
+ "label": "Remove column",
+ },
+ "showMoveLeft": true,
+ "showMoveRight": true,
+ },
+ "cellActions": Array [
+ [Function],
+ [Function],
+ [Function],
+ ],
+ "displayAsText": "extension",
+ "id": "extension",
+ "isSortable": true,
+ "schema": "string",
+ },
+ Object {
+ "actions": Object {
+ "additional": Array [
+ Object {
+ "data-test-subj": "gridCopyColumnNameToClipBoardButton",
+ "iconProps": Object {
+ "size": "m",
+ },
+ "iconType": "copyClipboard",
+ "label": ,
+ "onClick": [Function],
+ "size": "xs",
+ },
+ Object {
+ "data-test-subj": "gridCopyColumnValuesToClipBoardButton",
+ "iconProps": Object {
+ "size": "m",
+ },
+ "iconType": "copyClipboard",
+ "label": ,
+ "onClick": [Function],
+ "size": "xs",
+ },
+ ],
+ "showHide": Object {
+ "iconType": "cross",
+ "label": "Remove column",
+ },
+ "showMoveLeft": true,
+ "showMoveRight": true,
+ },
+ "cellActions": Array [
+ [Function],
+ ],
+ "displayAsText": "message",
+ "id": "message",
+ "isSortable": true,
+ "schema": "string",
+ },
+ ]
+ `);
+ });
});
diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx
index 6e4c0ec619e2b..b341d6236d235 100644
--- a/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx
+++ b/src/plugins/discover/public/components/discover_grid/discover_grid_columns.tsx
@@ -65,6 +65,7 @@ function buildEuiGridColumn({
dataView,
defaultColumns,
isSortEnabled,
+ isPlainRecord,
toastNotifications,
hasEditDataViewPermission,
valueToStringConverter,
@@ -77,6 +78,7 @@ function buildEuiGridColumn({
dataView: DataView;
defaultColumns: boolean;
isSortEnabled: boolean;
+ isPlainRecord?: boolean;
toastNotifications: ToastsStart;
hasEditDataViewPermission: () => boolean;
valueToStringConverter: ValueToStringConverter;
@@ -99,7 +101,7 @@ function buildEuiGridColumn({
const column: EuiDataGridColumn = {
id: columnName,
schema: getSchemaByKbnType(dataViewField?.type),
- isSortable: isSortEnabled && dataViewField?.sortable === true,
+ isSortable: isSortEnabled && (isPlainRecord || dataViewField?.sortable === true),
displayAsText: columnDisplayName,
actions: {
showHide:
@@ -176,6 +178,7 @@ export function getEuiGridColumns({
showTimeCol,
defaultColumns,
isSortEnabled,
+ isPlainRecord,
services,
hasEditDataViewPermission,
valueToStringConverter,
@@ -189,6 +192,7 @@ export function getEuiGridColumns({
showTimeCol: boolean;
defaultColumns: boolean;
isSortEnabled: boolean;
+ isPlainRecord?: boolean;
services: {
uiSettings: IUiSettingsClient;
toastNotifications: ToastsStart;
@@ -213,6 +217,7 @@ export function getEuiGridColumns({
dataView,
defaultColumns,
isSortEnabled,
+ isPlainRecord,
toastNotifications: services.toastNotifications,
hasEditDataViewPermission,
valueToStringConverter,
diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx
index fa0a74f5d0205..eb6788f9bfe7e 100644
--- a/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx
+++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx
@@ -258,7 +258,7 @@ export class SavedSearchEmbeddable
this.searchProps!.isLoading = false;
this.searchProps!.isPlainRecord = true;
this.searchProps!.showTimeCol = false;
- this.searchProps!.isSortEnabled = false;
+ this.searchProps!.isSortEnabled = true;
return;
}
diff --git a/test/functional/apps/discover/group2/_sql_view.ts b/test/functional/apps/discover/group2/_sql_view.ts
index e2cfb68cef5b5..6374ee405c70a 100644
--- a/test/functional/apps/discover/group2/_sql_view.ts
+++ b/test/functional/apps/discover/group2/_sql_view.ts
@@ -77,8 +77,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await testSubjects.exists('unifiedHistogramQueryHits')).to.be(true);
expect(await testSubjects.exists('discoverAlertsButton')).to.be(false);
expect(await testSubjects.exists('shareTopNavButton')).to.be(true);
+ expect(await testSubjects.exists('dataGridColumnSortingButton')).to.be(true);
expect(await testSubjects.exists('docTableExpandToggleColumn')).to.be(true);
- expect(await testSubjects.exists('dataGridColumnSortingButton')).to.be(false);
expect(await testSubjects.exists('fieldListFiltersFieldTypeFilterToggle')).to.be(true);
await testSubjects.click('field-@message-showDetails');
expect(await testSubjects.exists('discoverFieldListPanelEditItem')).to.be(false);
From f21f7ce6ca96c72ebcf02ae21d2f6885a429b27e Mon Sep 17 00:00:00 2001
From: Jean-Louis Leysens
Date: Fri, 2 Jun 2023 13:40:42 +0200
Subject: [PATCH 06/18] [HTTP/Docs] Clarify versioning practices for `internal`
vs `public` endpoints (#158828)
## Summary
Expands on the HTTP versioning tutorial with more information about
`internal` vs `public` endpoints.
## Screenshots
---
dev_docs/tutorials/versioning_http_apis.mdx | 39 ++++++++++++++++++---
1 file changed, 34 insertions(+), 5 deletions(-)
diff --git a/dev_docs/tutorials/versioning_http_apis.mdx b/dev_docs/tutorials/versioning_http_apis.mdx
index 81599028819b9..b0a4bf899158c 100644
--- a/dev_docs/tutorials/versioning_http_apis.mdx
+++ b/dev_docs/tutorials/versioning_http_apis.mdx
@@ -212,17 +212,45 @@ The changes are:
### 4. Adhere to the HTTP versioning specification
-#### Choosing the right version
-##### Public endpoints
+We categorize our endpoints based on their intended audience: `public` or `internal`. Different versioning practices apply to each.
+
+#### Public endpoints
Public endpoints include any endpoint that is intended for users to directly integrate with via HTTP.
+
+ All Kibana's public endpoints must be versioned using the format described below.
+
+
+##### Version lifecycle
+
+Introducing a new version or moving a current version into deprecation to eventually be deleted must
+follow [this process](https://github.com/elastic/dev/issues/new?assignees=&labels=breaking-change-proposal&projects=&template=breaking-change.md).
+
+##### Version format
+
Choose a date string in the format `YYYY-MM-DD`. This date should be the date that a (group) of APIs was made available.
-##### Internal endpoints
-Internal endpoints are all non-public endpoints (see definition above).
+--------
+
+#### Internal endpoints
+Internal endpoints are all non-public endpoints (see definition above). Note: these endpoints do not need to be versioned,
+but versioning can be leveraged to maintain BWC with existing clients.
-If you need to maintain backwards-compatibility for an internal endpoint use a single, larger-than-zero number. Ex. `1`.
+##### Version lifecycle
+
+Introducing/removing a version is up to the team who owns the HTTP API. Consider how introduction or removal might
+affect client code when being rolled out.
+
+
+ To keep maintenance light it is **highly** recommended to reduce the number of versions you have for internal endpoints. In your code it is possible to
+ centrally define and share internal versions through code that is `common` to your browser- and server-side plugin code.
+
+
+
+##### Version format
+
+If you need to version an internal endpoint use a single, larger-than-zero major version. Ex. `1`.
#### Use the versioned router
@@ -335,4 +363,5 @@ export class MyPlugin implements Plugin {
```
#### Additional reading
+
For more details on the versioning specification see [this document](https://docs.google.com/document/d/1YpF6hXIHZaHvwNaQAxWFzexUF1nbqACTtH2IfDu0ldA/edit?usp=sharing).
From a4889b48517ea79974a2eca584f0c7273a6f032a Mon Sep 17 00:00:00 2001
From: Pablo Machado
Date: Fri, 2 Jun 2023 13:42:15 +0200
Subject: [PATCH 07/18] Fix hover Action showing for zero Count on D&R
Dashboard (#158902)
issue: https://github.com/elastic/kibana/issues/158057
## Summary
Remove hover actions from the table when the count of alerts is zero.
**BEFORE**

**AFTER**

### 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
---
.../host_alerts_table.test.tsx | 27 +++
.../host_alerts_table/host_alerts_table.tsx | 184 +++++++++---------
.../user_alerts_table/user_alerts_table.tsx | 184 +++++++++---------
3 files changed, 217 insertions(+), 178 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx
index f0ef8f8d00da7..fb18a3a6a4459 100644
--- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.test.tsx
@@ -157,4 +157,31 @@ describe('HostAlertsTable', () => {
},
]);
});
+
+ it('should render cellActions when count is bigger than zero', () => {
+ mockUseHostAlertsItemsReturn({
+ items: [parsedVulnerableHostsAlertsResult[0]],
+ });
+ const { getAllByTestId } = renderComponent();
+
+ expect(getAllByTestId('cellActions-renderContent-host.name').length).toBe(5);
+ });
+
+ it('should not render cellActions when count is zero', () => {
+ mockUseHostAlertsItemsReturn({
+ items: [
+ {
+ hostName: 'Host-342m5gl1g2',
+ totalAlerts: 100,
+ critical: 0,
+ high: 0,
+ low: 0,
+ medium: 0,
+ },
+ ],
+ });
+ const { getAllByTestId } = renderComponent();
+
+ expect(getAllByTestId('cellActions-renderContent-host.name').length).toBe(1);
+ });
});
diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx
index 5a3d2462e3e9a..8ac908bad945a 100644
--- a/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/host_alerts_table/host_alerts_table.tsx
@@ -179,30 +179,33 @@ const getTableColumns: GetTableColumns = (handleClick) => [
name: i18n.STATUS_CRITICAL_LABEL,
render: (count: number, { hostName }) => (
-
- handleClick({ hostName, severity: 'critical' })}
+ {count > 0 ? (
+
-
-
-
+ handleClick({ hostName, severity: 'critical' })}
+ >
+
+
+
+ ) : (
+
+ )}
),
},
@@ -211,29 +214,30 @@ const getTableColumns: GetTableColumns = (handleClick) => [
name: i18n.STATUS_HIGH_LABEL,
render: (count: number, { hostName }) => (
-
- handleClick({ hostName, severity: 'high' })}
+ {count > 0 ? (
+
-
-
-
+ handleClick({ hostName, severity: 'high' })}>
+
+
+
+ ) : (
+
+ )}
),
},
@@ -242,29 +246,30 @@ const getTableColumns: GetTableColumns = (handleClick) => [
name: i18n.STATUS_MEDIUM_LABEL,
render: (count: number, { hostName }) => (
-
- handleClick({ hostName, severity: 'medium' })}
+ {count > 0 ? (
+
-
-
-
+ handleClick({ hostName, severity: 'medium' })}>
+
+
+
+ ) : (
+
+ )}
),
},
@@ -273,29 +278,30 @@ const getTableColumns: GetTableColumns = (handleClick) => [
name: i18n.STATUS_LOW_LABEL,
render: (count: number, { hostName }) => (
-
- handleClick({ hostName, severity: 'low' })}
+ {count > 0 ? (
+
-
-
-
+ handleClick({ hostName, severity: 'low' })}>
+
+
+
+ ) : (
+
+ )}
),
},
diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx
index 875556f12fb8d..d63e5b88e3660 100644
--- a/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/user_alerts_table/user_alerts_table.tsx
@@ -176,30 +176,33 @@ const getTableColumns: GetTableColumns = (handleClick) => [
name: i18n.STATUS_CRITICAL_LABEL,
render: (count: number, { userName }) => (
-
- handleClick({ userName, severity: 'critical' })}
+ {count > 0 ? (
+
-
-
-
+ handleClick({ userName, severity: 'critical' })}
+ >
+
+
+
+ ) : (
+
+ )}
),
},
@@ -208,29 +211,30 @@ const getTableColumns: GetTableColumns = (handleClick) => [
name: i18n.STATUS_HIGH_LABEL,
render: (count: number, { userName }) => (
-
- handleClick({ userName, severity: 'high' })}
+ {count > 0 ? (
+
-
-
-
+ handleClick({ userName, severity: 'high' })}>
+
+
+
+ ) : (
+
+ )}
),
},
@@ -239,29 +243,30 @@ const getTableColumns: GetTableColumns = (handleClick) => [
name: i18n.STATUS_MEDIUM_LABEL,
render: (count: number, { userName }) => (
-
- handleClick({ userName, severity: 'medium' })}
+ {count > 0 ? (
+
-
-
-
+ handleClick({ userName, severity: 'medium' })}>
+
+
+
+ ) : (
+
+ )}
),
},
@@ -270,29 +275,30 @@ const getTableColumns: GetTableColumns = (handleClick) => [
name: i18n.STATUS_LOW_LABEL,
render: (count: number, { userName }) => (
-
- handleClick({ userName, severity: 'low' })}
+ {count > 0 ? (
+
-
-
-
+ handleClick({ userName, severity: 'low' })}>
+
+
+
+ ) : (
+
+ )}
),
},
From 801919702ae03d8946c4a5677d387566f43f615c Mon Sep 17 00:00:00 2001
From: Jeramy Soucy
Date: Fri, 2 Jun 2023 13:58:29 +0200
Subject: [PATCH 08/18] [RCS 2.0] Bypasses query for field list when
configuring FLS against a remote cluster (#158728)
Closes #158280
## Summary
This PR implements an index type (local vs remote) check within the
index privilege form in order to bypass querying the field list from the
index API client for remote cluster indices.
## Testing
- Augmented functional tests in `index_privilege_form.test.tsx` with
'_does not query availble fields for remote cluster indices_' test case.
---
.../es/index_privilege_form.test.tsx | 20 +++++++++++++++++++
.../privileges/es/index_privilege_form.tsx | 7 ++++++-
2 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx
index 8ed57e646326f..09160ce3b6967 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx
@@ -416,6 +416,26 @@ describe('field level security', () => {
expect(testProps.indicesAPIClient.getFields).toHaveBeenCalledWith('newPattern');
});
+ test('does not query availble fields for remote cluster indices', async () => {
+ const testProps = {
+ ...props,
+ indexType: 'remote_indices' as const,
+ indexPrivilege: {
+ ...props.indexPrivilege,
+ clusters: ['test-cluster'],
+ names: ['foo', 'bar-*'],
+ },
+ indicesAPIClient: indicesAPIClientMock.create(),
+ allowFieldLevelSecurity: true,
+ };
+
+ testProps.indicesAPIClient.getFields.mockResolvedValue(['a', 'b', 'c']);
+
+ mountWithIntl();
+ await nextTick();
+ expect(testProps.indicesAPIClient.getFields).not.toHaveBeenCalled();
+ });
+
test('it displays a warning when no fields are granted', () => {
const testProps = {
...props,
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx
index 58d1bd8061bf8..2540316caac89 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx
@@ -227,7 +227,12 @@ export class IndexPrivilegeForm extends Component {
};
private loadFLSOptions = (indexNames: string[], force = false) => {
- if (!force && (this.isFieldListLoading || indexNames.length === 0)) return;
+ if (
+ this.props.indexType === 'remote_indices' ||
+ (!force && (this.isFieldListLoading || indexNames.length === 0))
+ ) {
+ return;
+ }
this.isFieldListLoading = true;
this.setState({
From 238d5bb825de4f16fb3b84b0b7272d249474c2c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Fri, 2 Jun 2023 13:50:48 +0100
Subject: [PATCH 09/18] [SharedUxChromeNavigation V2] Add EUI component to
building blocks (#158297)
---
.../src/project_navigation.ts | 29 +-
.../src/ui/v2/components/cloud_link.tsx | 64 ++
.../navigation/src/ui/v2/components/index.ts | 2 +
.../src/ui/v2/components/navigation.test.tsx | 407 ++++++++++---
.../src/ui/v2/components/navigation.tsx | 72 ++-
.../ui/v2/components/navigation_bucket.tsx | 75 +++
.../ui/v2/components/navigation_footer.tsx | 28 +
.../src/ui/v2/components/navigation_group.tsx | 94 +--
.../ui/v2/components/navigation_header.tsx | 61 ++
.../src/ui/v2/components/navigation_item.tsx | 86 ++-
.../v2/components/navigation_section_ui.tsx | 82 +++
.../src/ui/v2/components/navigation_ui.tsx | 44 ++
.../ui/v2/components/recently_accessed.tsx | 64 ++
.../ui/v2/default_navigation.test.helpers.ts | 449 ++++++++++++++
.../src/ui/v2/default_navigation.test.tsx | 399 ++++++++++---
.../src/ui/v2/default_navigation.tsx | 151 +++--
.../navigation/src/ui/v2/hooks/index.ts | 9 +
.../src/ui/v2/hooks/use_init_navnode.ts | 221 +++++++
.../v2/{ => hooks}/use_register_tree_node.ts | 8 +-
.../src/ui/v2/nav_tree_presets/analytics.ts | 37 ++
.../src/ui/v2/nav_tree_presets/devtools.ts | 46 ++
.../src/ui/v2/nav_tree_presets/index.ts | 67 +++
.../src/ui/v2/nav_tree_presets/management.ts | 226 +++++++
.../src/ui/v2/nav_tree_presets/ml.ts | 136 +++++
.../src/ui/v2/navigation.stories.tsx | 564 ++++++++++++++----
.../chrome/navigation/src/ui/v2/types.ts | 179 +++++-
.../navigation/src/ui/v2/use_init_navnode.ts | 181 ------
27 files changed, 3143 insertions(+), 638 deletions(-)
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/components/cloud_link.tsx
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_bucket.tsx
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_footer.tsx
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_header.tsx
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_section_ui.tsx
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_ui.tsx
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/components/recently_accessed.tsx
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.helpers.ts
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/hooks/index.ts
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/hooks/use_init_navnode.ts
rename packages/shared-ux/chrome/navigation/src/ui/v2/{ => hooks}/use_register_tree_node.ts (75%)
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/analytics.ts
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/devtools.ts
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/index.ts
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/management.ts
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/ml.ts
delete mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/use_init_navnode.ts
diff --git a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts
index deea1a9cb3d52..cd8f634c07836 100644
--- a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts
+++ b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts
@@ -7,6 +7,7 @@
*/
import type { ComponentType } from 'react';
+import type { ChromeNavLink } from './nav_links';
/** @internal */
type AppId = string;
@@ -17,20 +18,38 @@ type DeepLinkId = string;
/** @internal */
export type AppDeepLinkId = `${AppId}:${DeepLinkId}`;
-/** @public */
+/**
+ * @public
+ *
+ * App id or deeplink id
+ */
export type ChromeProjectNavigationLink = AppId | AppDeepLinkId;
/** @public */
export interface ChromeProjectNavigationNode {
- id?: string;
- link?: ChromeProjectNavigationLink;
- children?: ChromeProjectNavigationNode[];
- title?: string;
+ /** Optional id, if not passed a "link" must be provided. */
+ id: string;
+ /** Optional title. If not provided and a "link" is provided the title will be the Deep link title */
+ title: string;
+ /** Path in the tree of the node */
+ path: string[];
+ /** App id or deeplink id */
+ deepLink?: ChromeNavLink;
+ /** Optional icon for the navigation node. Note: not all navigation depth will render the icon */
icon?: string;
+ /** Optional children of the navigation node */
+ children?: ChromeProjectNavigationNode[];
}
/** @public */
export interface ChromeProjectNavigation {
+ /**
+ * The URL href for the home link
+ */
+ homeRef: string;
+ /**
+ * The navigation tree representation of the side bar navigation.
+ */
navigationTree: ChromeProjectNavigationNode[];
}
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/cloud_link.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/cloud_link.tsx
new file mode 100644
index 0000000000000..8e8ac169eded9
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/cloud_link.tsx
@@ -0,0 +1,64 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import { EuiCollapsibleNavGroup, EuiLink } from '@elastic/eui';
+import React, { FC } from 'react';
+import { getI18nStrings } from '../../i18n_strings';
+
+const i18nTexts = getI18nStrings();
+
+const presets = {
+ projects: {
+ href: 'https://cloud.elastic.co/projects',
+ icon: 'spaces',
+ title: i18nTexts.linkToCloudProjects,
+ dataTestSubj: 'nav-header-link-to-projects',
+ },
+ deployments: {
+ href: 'https://cloud.elastic.co/deployments',
+ icon: 'spaces',
+ title: i18nTexts.linkToCloudDeployments,
+ dataTestSubj: 'nav-header-link-to-deployments',
+ },
+};
+
+export interface Props {
+ /** Use one of the cloud link presets */
+ preset?: 'projects' | 'deployments' | null;
+ /** Optional. If "preset" is not provided it is required */
+ href?: string;
+ /** Optional. If "preset" is not provided it is required */
+ icon?: string;
+ /** Optional. If "preset" is not provided it is required */
+ title?: string;
+}
+
+export const CloudLink: FC = ({ preset, href: _href, icon: _icon, title: _title }) => {
+ if (preset === null) {
+ return null;
+ }
+
+ if (!preset && (!_href || !_icon || !_title)) {
+ throw new Error(`Navigation.CloudLink requires href, icon, and title`);
+ }
+
+ const { href, icon, title, dataTestSubj } =
+ preset && presets[preset]
+ ? presets[preset]!
+ : {
+ href: _href,
+ icon: _icon,
+ title: _title,
+ dataTestSubj: 'nav-header-link-to-cloud',
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/index.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/components/index.ts
index 50ab11e2263b7..bb7aeacdfabb7 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/index.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/index.ts
@@ -6,4 +6,6 @@
* Side Public License, v 1.
*/
+export type { Props as CloudLinkProps } from './cloud_link';
export { Navigation } from './navigation';
+export type { Props as RecentlyAccessedProps } from './recently_accessed';
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.test.tsx
index 2f5a3781cb3a2..80bc5cd0c7abe 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.test.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.test.tsx
@@ -14,16 +14,101 @@ import type { ChromeNavLink } from '@kbn/core-chrome-browser';
import { getServicesMock } from '../../../../mocks/src/jest';
import { NavigationProvider } from '../../../services';
import { Navigation } from './navigation';
+import { defaultNavigationTree } from '../default_navigation.test.helpers';
describe('', () => {
const services = getServicesMock();
describe('builds the navigation tree', () => {
+ test('render reference UI and build the navigation tree', async () => {
+ const onProjectNavigationChange = jest.fn();
+
+ const { findByTestId } = render(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ expect(await findByTestId('nav-item-group1.item1')).toBeVisible();
+ expect(await findByTestId('nav-item-group1.item2')).toBeVisible();
+ expect(await findByTestId('nav-item-group1.group1A')).toBeVisible();
+ expect(await findByTestId('nav-item-group1.group1A.item1')).toBeVisible();
+ expect(await findByTestId('nav-item-group1.group1A.group1A_1')).toBeVisible();
+
+ // Click the last group to expand and show the last depth
+ (await findByTestId('nav-item-group1.group1A.group1A_1')).click();
+
+ expect(await findByTestId('nav-item-group1.group1A.group1A_1.item1')).toBeVisible();
+
+ expect(onProjectNavigationChange).toHaveBeenCalled();
+ const lastCall =
+ onProjectNavigationChange.mock.calls[onProjectNavigationChange.mock.calls.length - 1];
+ const [navTree] = lastCall;
+
+ expect(navTree).toEqual({
+ homeRef: 'https://elastic.co',
+ navigationTree: [
+ {
+ id: 'group1',
+ path: ['group1'],
+ title: '',
+ children: [
+ {
+ id: 'item1',
+ title: 'Item 1',
+ path: ['group1', 'item1'],
+ },
+ {
+ id: 'item2',
+ title: 'Item 2',
+ path: ['group1', 'item2'],
+ },
+ {
+ id: 'group1A',
+ title: 'Group1A',
+ path: ['group1', 'group1A'],
+ children: [
+ {
+ id: 'item1',
+ title: 'Group 1A Item 1',
+ path: ['group1', 'group1A', 'item1'],
+ },
+ {
+ id: 'group1A_1',
+ title: 'Group1A_1',
+ path: ['group1', 'group1A', 'group1A_1'],
+ children: [
+ {
+ id: 'item1',
+ title: 'Group 1A_1 Item 1',
+ path: ['group1', 'group1A', 'group1A_1', 'item1'],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ });
+ });
+
test('should read the title from props, children or deeplink', async () => {
const navLinks$: Observable = of([
{
- id: 'item3',
- title: 'Title from deeplink!',
+ id: 'item1',
+ title: 'Title from deeplink',
baseUrl: '',
url: '',
href: '',
@@ -38,19 +123,16 @@ describe('', () => {
navLinks$={navLinks$}
onProjectNavigationChange={onProjectNavigationChange}
>
-
- Title in children
-
- {/* Title will be read from the deeplink */}
-
- {/* Title will be read from the props */}
-
- {/* Title will be read from the children */}
-
- Override the deeplink with children
-
- {/* Should not appear */}
-
+
+
+
+ {/* Title from deeplink */}
+
+
+
+ Title in children
+
+
);
@@ -61,57 +143,138 @@ describe('', () => {
const [navTree] = lastCall;
expect(navTree).toEqual({
+ homeRef: 'https://elastic.co',
navigationTree: [
{
- id: 'item1',
- title: 'Title in children',
- },
- {
- id: 'item2',
- title: 'Title in props',
- },
- {
- id: 'item3-a',
- title: 'Title from deeplink!',
- deepLink: {
- id: 'item3',
- title: 'Title from deeplink!',
- baseUrl: '',
- url: '',
- href: '',
- },
- },
- {
- id: 'item3-b',
- title: 'Override the deeplink with props',
- deepLink: {
- id: 'item3',
- title: 'Title from deeplink!',
- baseUrl: '',
- url: '',
- href: '',
- },
+ id: 'root',
+ path: ['root'],
+ title: '',
+ children: [
+ {
+ id: 'group1',
+ path: ['root', 'group1'],
+ title: '',
+ children: [
+ {
+ id: 'item1',
+ path: ['root', 'group1', 'item1'],
+ title: 'Title from deeplink',
+ deepLink: {
+ id: 'item1',
+ title: 'Title from deeplink',
+ baseUrl: '',
+ url: '',
+ href: '',
+ },
+ },
+ {
+ id: 'item2',
+ title: 'Overwrite deeplink title',
+ path: ['root', 'group1', 'item2'],
+ deepLink: {
+ id: 'item1',
+ title: 'Title from deeplink',
+ baseUrl: '',
+ url: '',
+ href: '',
+ },
+ },
+ {
+ id: 'item3',
+ title: 'Title in props',
+ path: ['root', 'group1', 'item3'],
+ },
+ {
+ id: 'item4',
+ path: ['root', 'group1', 'item4'],
+ title: 'Title in children',
+ },
+ ],
+ },
+ ],
},
+ ],
+ });
+ });
+
+ test('should filter out unknown deeplinks', async () => {
+ const navLinks$: Observable = of([
+ {
+ id: 'item1',
+ title: 'Title from deeplink',
+ baseUrl: '',
+ url: '',
+ href: '',
+ },
+ ]);
+
+ const onProjectNavigationChange = jest.fn();
+
+ const { findByTestId } = render(
+
+
+
+
+ {/* Title from deeplink */}
+
+ {/* Should not appear */}
+
+
+
+
+
+ );
+
+ expect(await findByTestId('nav-item-root.group1.item1')).toBeVisible();
+ expect(await findByTestId('nav-item-root.group1.item1')).toBeVisible();
+
+ expect(onProjectNavigationChange).toHaveBeenCalled();
+ const lastCall =
+ onProjectNavigationChange.mock.calls[onProjectNavigationChange.mock.calls.length - 1];
+ const [navTree] = lastCall;
+
+ expect(navTree).toEqual({
+ homeRef: 'https://elastic.co',
+ navigationTree: [
{
- id: 'item3-c',
- title: 'Override the deeplink with children',
- deepLink: {
- id: 'item3',
- title: 'Title from deeplink!',
- baseUrl: '',
- url: '',
- href: '',
- },
+ id: 'root',
+ path: ['root'],
+ title: '',
+ children: [
+ {
+ id: 'group1',
+ path: ['root', 'group1'],
+ title: '',
+ children: [
+ {
+ id: 'item1',
+ path: ['root', 'group1', 'item1'],
+ title: 'Title from deeplink',
+ deepLink: {
+ id: 'item1',
+ title: 'Title from deeplink',
+ baseUrl: '',
+ url: '',
+ href: '',
+ },
+ },
+ ],
+ },
+ ],
},
],
});
});
- test('should render any level of depth', async () => {
+ test('should render custom react element', async () => {
const navLinks$: Observable = of([
{
- id: 'item3',
- title: 'Title from deeplink!',
+ id: 'item1',
+ title: 'Title from deeplink',
baseUrl: '',
url: '',
href: '',
@@ -120,56 +283,69 @@ describe('', () => {
const onProjectNavigationChange = jest.fn();
- render(
+ const { findByTestId } = render(
-
-
-
- {/* Will read the title from the deeplink */}
-
-
-
+
+
+
+
+ Custom element
+
+
+ {(navNode) => {navNode.title}
}
+
);
+ expect(await findByTestId('my-custom-element')).toBeVisible();
+ expect(await findByTestId('my-other-custom-element')).toBeVisible();
+ expect(await (await findByTestId('my-other-custom-element')).textContent).toBe(
+ 'Children prop'
+ );
+
expect(onProjectNavigationChange).toHaveBeenCalled();
const lastCall =
onProjectNavigationChange.mock.calls[onProjectNavigationChange.mock.calls.length - 1];
const [navTree] = lastCall;
expect(navTree).toEqual({
+ homeRef: 'https://elastic.co',
navigationTree: [
{
- id: 'item1',
- title: 'Item 1',
+ id: 'root',
+ path: ['root'],
+ title: '',
children: [
{
- id: 'item1',
- title: 'Item 1',
+ id: 'group1',
+ path: ['root', 'group1'],
+ title: '',
children: [
{
- id: 'item3',
- title: 'Title from deeplink!',
+ id: 'item1',
+ path: ['root', 'group1', 'item1'],
+ title: 'Title from deeplink',
+ renderItem: expect.any(Function),
deepLink: {
+ id: 'item1',
+ title: 'Title from deeplink',
baseUrl: '',
- href: '',
- id: 'item3',
- title: 'Title from deeplink!',
url: '',
+ href: '',
},
- children: [
- {
- id: 'item1',
- title: 'Item 1',
- },
- ],
+ },
+ {
+ id: 'item2',
+ path: ['root', 'group1', 'item2'],
+ title: 'Children prop',
+ renderItem: expect.any(Function),
},
],
},
@@ -179,9 +355,78 @@ describe('', () => {
});
});
- test.skip('does not render in the UI the nodes that points to unexisting deeplinks', async () => {
- // TODO: This test will be added when we'll have the UI and be able to add
- // data-test-subj to all the nodes with their paths
+ test('should render group preset (analytics, ml...)', async () => {
+ const onProjectNavigationChange = jest.fn();
+
+ render(
+
+
+
+
+
+
+
+
+ );
+
+ expect(onProjectNavigationChange).toHaveBeenCalled();
+ const lastCall =
+ onProjectNavigationChange.mock.calls[onProjectNavigationChange.mock.calls.length - 1];
+ const [navTree] = lastCall;
+
+ expect(navTree).toEqual({
+ homeRef: 'https://elastic.co',
+ navigationTree: defaultNavigationTree.map(({ type, ...rest }) => rest),
+ });
+ });
+
+ test('should render cloud link', async () => {
+ const onProjectNavigationChange = jest.fn();
+
+ const { findByTestId } = render(
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+ expect(await findByTestId('nav-header-link-to-projects')).toBeVisible();
+ expect(await findByTestId('nav-header-link-to-deployments')).toBeVisible();
+ expect(await findByTestId('nav-header-link-to-cloud')).toBeVisible();
+ expect(await (await findByTestId('nav-header-link-to-cloud')).textContent).toBe(
+ 'Custom link'
+ );
+ });
+
+ test('should render recently accessed items', async () => {
+ const recentlyAccessed$ = of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]);
+
+ const { findByTestId } = render(
+
+
+
+
+
+
+
+
+
+ );
+
+ expect(await findByTestId('nav-bucket-recentlyAccessed')).toBeVisible();
+ expect(await (await findByTestId('nav-bucket-recentlyAccessed')).textContent).toBe(
+ 'RecentThis is an exampleAnother example'
+ );
});
});
});
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.tsx
index 378648c26012d..2cbbe766059d6 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.tsx
@@ -16,14 +16,21 @@ import React, {
useContext,
useRef,
} from 'react';
+import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
import { useNavigation as useNavigationServices } from '../../../services';
-import { InternalNavigationNode, RegisterFunction } from '../types';
+import { RegisterFunction, UnRegisterFunction } from '../types';
+import { CloudLink } from './cloud_link';
+import { NavigationFooter } from './navigation_footer';
import { NavigationGroup } from './navigation_group';
import { NavigationItem } from './navigation_item';
+import { NavigationUI } from './navigation_ui';
+import { RecentlyAccessed } from './recently_accessed';
interface Context {
register: RegisterFunction;
+ updateFooterChildren: (children: ReactNode) => void;
+ unstyled: boolean;
}
const NavigationContext = createContext({
@@ -31,14 +38,24 @@ const NavigationContext = createContext({
unregister: () => {},
path: [],
}),
+ updateFooterChildren: () => {},
+ unstyled: false,
});
interface Props {
children: ReactNode;
- onRootItemRemove?: (id: string) => void;
+ /**
+ * Href to the home page
+ */
+ homeRef: string;
+ /**
+ * Flag to indicate if the Navigation should not be styled with EUI components.
+ * If set to true, the children will be rendered as is.
+ */
+ unstyled?: boolean;
}
-export function Navigation({ children, onRootItemRemove }: Props) {
+export function Navigation({ children, homeRef, unstyled = false }: Props) {
const { onProjectNavigationChange } = useNavigationServices();
// We keep a reference of the order of the children that register themselves when mounting.
@@ -47,12 +64,21 @@ export function Navigation({ children, onRootItemRemove }: Props) {
const orderChildrenRef = useRef>({});
const idx = useRef(0);
- const [navigationItems, setNavigationItems] = useState>(
- {}
- );
+ const [navigationItems, setNavigationItems] = useState<
+ Record
+ >({});
+ const [footerChildren, setFooterChildren] = useState(null);
+
+ const unregister: UnRegisterFunction = useCallback((id: string) => {
+ setNavigationItems((prevItems) => {
+ const updatedItems = { ...prevItems };
+ delete updatedItems[id];
+ return updatedItems;
+ });
+ }, []);
const register = useCallback(
- (navNode: InternalNavigationNode) => {
+ (navNode: ChromeProjectNavigationNode) => {
orderChildrenRef.current[navNode.id] = idx.current++;
setNavigationItems((prevItems) => {
@@ -63,44 +89,39 @@ export function Navigation({ children, onRootItemRemove }: Props) {
});
return {
- unregister: () => {
- if (onRootItemRemove) {
- onRootItemRemove(navNode.id);
- }
-
- setNavigationItems((prevItems) => {
- const updatedItems = { ...prevItems };
- delete updatedItems[navNode.id];
- return updatedItems;
- });
- },
- path: [],
+ unregister,
+ path: [navNode.id],
};
},
- [onRootItemRemove]
+ [unregister]
);
const contextValue = useMemo(
() => ({
register,
+ updateFooterChildren: setFooterChildren,
+ unstyled,
}),
- [register]
+ [register, unstyled]
);
useEffect(() => {
- // Send the navigation tree to the Chrome service
+ // This will update the navigation tree in the Chrome service (calling the serverless.setNavigation())
onProjectNavigationChange({
+ homeRef,
navigationTree: Object.values(navigationItems).sort((a, b) => {
const aOrder = orderChildrenRef.current[a.id];
const bOrder = orderChildrenRef.current[b.id];
return aOrder - bOrder;
}),
});
- }, [navigationItems, onProjectNavigationChange]);
+ }, [navigationItems, onProjectNavigationChange, homeRef]);
return (
-
+
+ {children}
+
);
}
@@ -115,3 +136,6 @@ export function useNavigation() {
Navigation.Group = NavigationGroup;
Navigation.Item = NavigationItem;
+Navigation.Footer = NavigationFooter;
+Navigation.CloudLink = CloudLink;
+Navigation.RecentlyAccessed = RecentlyAccessed;
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_bucket.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_bucket.tsx
new file mode 100644
index 0000000000000..5d8cc8a989e0c
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_bucket.tsx
@@ -0,0 +1,75 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { FC, useCallback } from 'react';
+
+import { analytics, devtools, ml, management } from '../nav_tree_presets';
+import { Navigation } from './navigation';
+import type { NavigationGroupPreset, NodeDefinition } from '../types';
+
+const navTreePresets: { [preset in NavigationGroupPreset]: NodeDefinition } = {
+ analytics,
+ ml,
+ devtools,
+ management,
+};
+
+export interface Props {
+ preset?: NavigationGroupPreset;
+ nodeDefinition?: NodeDefinition;
+ defaultIsCollapsed?: boolean;
+}
+
+export const NavigationBucket: FC = ({
+ nodeDefinition: _nodeDefinition,
+ defaultIsCollapsed,
+ preset,
+}) => {
+ const nodeDefinition = preset ? navTreePresets[preset] : _nodeDefinition;
+
+ if (!nodeDefinition) {
+ throw new Error('Either preset or nodeDefinition must be defined');
+ }
+
+ const renderItems = useCallback(
+ (items: NodeDefinition[], isRoot = false) => {
+ return items.map((item) => {
+ const id = item.id ?? item.link;
+
+ if (!id) {
+ throw new Error(
+ `At least one of id or link must be defined for navigation item ${item.title}`
+ );
+ }
+
+ return (
+
+ {item.children ? (
+
+ {renderItems(item.children)}
+
+ ) : (
+
+ )}
+
+ );
+ });
+ },
+ [defaultIsCollapsed]
+ );
+
+ return <>{renderItems([nodeDefinition], true)}>;
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_footer.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_footer.tsx
new file mode 100644
index 0000000000000..d4b78c1e93053
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_footer.tsx
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { useEffect } from 'react';
+import { useNavigation } from './navigation';
+
+export interface Props {
+ children?: React.ReactNode;
+}
+
+function NavigationFooterComp({ children }: Props) {
+ const { updateFooterChildren } = useNavigation();
+
+ useEffect(() => {
+ if (children) {
+ updateFooterChildren(children);
+ }
+ }, [children, updateFooterChildren]);
+
+ return null;
+}
+
+export const NavigationFooter = React.memo(NavigationFooterComp);
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_group.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_group.tsx
index 71d33b62f432b..aa896f6bf45e5 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_group.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_group.tsx
@@ -8,16 +8,18 @@
import React, { createContext, useCallback, useMemo, useContext } from 'react';
-import { EuiButton } from '@elastic/eui';
-import { useInitNavnode } from '../use_init_navnode';
-import { NodeProps, RegisterFunction } from '../types';
-
-export const NavigationGroupContext = createContext(undefined);
+import { useInitNavNode } from '../hooks';
+import type { NodeProps, RegisterFunction } from '../types';
+import { NavigationSectionUI } from './navigation_section_ui';
+import { useNavigation } from './navigation';
+import { NavigationBucket, Props as NavigationBucketProps } from './navigation_bucket';
interface Context {
register: RegisterFunction;
}
+export const NavigationGroupContext = createContext(undefined);
+
export function useNavigationGroup(
throwIfNotFound: T = true as T
): T extends true ? Context : Context | undefined {
@@ -28,45 +30,48 @@ export function useNavigationGroup(
return context as T extends true ? Context : Context | undefined;
}
-function NavigationGroupComp(node: NodeProps) {
- const { children, onRemove } = node;
- const { navNode, registerChildNode } = useInitNavnode(node);
- const { title, deepLink } = navNode ?? {};
-
- const wrapTextWithLink = useCallback(
- (text?: string) =>
- deepLink ? (
-
- {text}
-
- ) : (
- text
- ),
- [deepLink]
- );
+export interface Props extends NodeProps {
+ unstyled?: boolean;
+ defaultIsCollapsed?: boolean;
+}
- const renderTempUIToTestRemoveBehavior = useCallback(
- () =>
- onRemove ? (
- <>
- {' '}
- onRemove()}>
- Remove
-
- >
- ) : null,
- [onRemove]
- );
+function NavigationGroupInternalComp(props: Props) {
+ const navigationContext = useNavigation();
+ const { children, defaultIsCollapsed, ...node } = props;
+ const { navNode, registerChildNode, path, childrenNodes } = useInitNavNode(node);
+
+ const unstyled = props.unstyled ?? navigationContext.unstyled;
const renderContent = useCallback(() => {
+ if (!path || !navNode) {
+ return null;
+ }
+
+ if (unstyled) {
+ // No UI for unstyled groups
+ return children;
+ }
+
+ // Each "top level" group is rendered using the EuiCollapsibleNavGroup component
+ // inside the NavigationSectionUI. That's how we get the "collapsible" behavior.
+ const isTopLevel = path && path.length === 1;
+
return (
<>
- {wrapTextWithLink(title)}
- {renderTempUIToTestRemoveBehavior()}
-
+ {isTopLevel && (
+
+ )}
+ {/* We render the children so they mount and can register themselves but
+ visually they don't appear here in the DOM. They are rendered inside the
+ "items" prop (see ) */}
+ {children}
>
);
- }, [children, renderTempUIToTestRemoveBehavior, title, wrapTextWithLink]);
+ }, [navNode, path, childrenNodes, children, defaultIsCollapsed, unstyled]);
const contextValue = useMemo(() => {
return {
@@ -80,10 +85,19 @@ function NavigationGroupComp(node: NodeProps) {
return (
- {/* Note: temporary UI. In future PR we'll have an EUI component here */}
- {renderContent()}
+ {renderContent()}
);
}
-export const NavigationGroup = React.memo(NavigationGroupComp);
+function NavigationGroupComp(props: Props & NavigationBucketProps) {
+ if (props.preset) {
+ const { id, title, link, icon, children, ...rest } = props;
+ return ;
+ }
+
+ const { preset, nodeDefinition, ...rest } = props;
+ return ;
+}
+
+export const NavigationGroup = React.memo(NavigationGroupComp) as typeof NavigationGroupComp;
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_header.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_header.tsx
new file mode 100644
index 0000000000000..92f93e8092979
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_header.tsx
@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { FC } from 'react';
+import { EuiFlexGroup, EuiFlexItem, EuiHeaderLogo, EuiLoadingSpinner } from '@elastic/eui';
+import useObservable from 'react-use/lib/useObservable';
+import { useNavigation as useServices } from '../../../services';
+import { ElasticMark } from '../../elastic_mark';
+import { getI18nStrings } from '../../i18n_strings';
+
+import '../../header_logo.scss';
+
+interface Props {
+ homeHref: string;
+}
+
+export const NavHeader: FC = ({ homeHref }) => {
+ const strings = getI18nStrings();
+ const { basePath, navigateToUrl, loadingCount$ } = useServices();
+ const loadingCount = useObservable(loadingCount$, 0);
+ const homeUrl = basePath.prepend(homeHref);
+
+ const navigateHome = (event: React.MouseEvent) => {
+ event.preventDefault();
+ navigateToUrl(homeUrl);
+ };
+
+ const logo =
+ loadingCount === 0 ? (
+
+ ) : (
+
+
+
+ );
+
+ return (
+
+ {logo}
+
+
+
+
+ );
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_item.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_item.tsx
index 0aedd52fb2e07..44ffe44572d6e 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_item.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_item.tsx
@@ -6,65 +6,55 @@
* Side Public License, v 1.
*/
-import { EuiButton } from '@elastic/eui';
-import React, { useCallback } from 'react';
+import React, { Fragment, ReactElement, ReactNode, useEffect } from 'react';
-import { NodeProps } from '../types';
-import { useInitNavnode } from '../use_init_navnode';
+import type { ChromeProjectNavigationNodeEnhanced, NodeProps } from '../types';
+import { useInitNavNode } from '../hooks';
+import { useNavigation } from './navigation';
-function NavigationItemComp(node: NodeProps) {
- const { children, onRemove } = node;
- const { navNode } = useInitNavnode(node);
- const { title, deepLink } = navNode ?? {};
+export interface Props extends NodeProps {
+ element?: string;
+ unstyled?: boolean;
+}
- // Note: temporary UI. In future PR we'll have an EUI component here
- const wrapTextWithLink = useCallback(
- (text?: string) =>
- deepLink ? (
-
- {text}
-
- ) : (
- text
- ),
- [deepLink]
- );
+function isReactElement(element: ReactNode): element is ReactElement {
+ return React.isValidElement(element);
+}
- const renderContent = useCallback(() => {
- if (!children) {
- return wrapTextWithLink(title);
- }
+function NavigationItemComp(props: Props) {
+ const navigationContext = useNavigation();
+ const navNodeRef = React.useRef(null);
- if (typeof children === 'string') {
- return wrapTextWithLink(children);
- } else if (typeof children === 'function') {
- return children(deepLink);
- }
+ const { element, children, ...node } = props;
+ const unstyled = props.unstyled ?? navigationContext.unstyled;
+
+ let renderItem: (() => ReactElement) | undefined;
- return children;
- }, [children, deepLink, title, wrapTextWithLink]);
+ if (!unstyled && children && (typeof children === 'function' || isReactElement(children))) {
+ renderItem =
+ typeof children === 'function' ? () => children(navNodeRef.current) : () => children;
+ }
- const renderTempUIToTestRemoveBehavior = () =>
- onRemove ? (
- <>
- {' '}
- onRemove()}>
- Remove
-
- >
- ) : null;
+ const { navNode } = useInitNavNode({ ...node, children, renderItem });
- if (!navNode) {
+ useEffect(() => {
+ navNodeRef.current = navNode;
+ }, [navNode]);
+
+ if (!navNode || !unstyled) {
return null;
}
- return (
- // Note: temporary UI. In future PR we'll have an EUI component here
-
- {renderContent()}
- {renderTempUIToTestRemoveBehavior()}
-
- );
+ if (children) {
+ if (typeof children === 'function') {
+ return children(navNode);
+ }
+ return <>{children}>;
+ }
+
+ const Element = element || Fragment;
+
+ return {navNode.title};
}
export const NavigationItem = React.memo(NavigationItemComp);
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_section_ui.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_section_ui.tsx
new file mode 100644
index 0000000000000..1efa885ace4aa
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_section_ui.tsx
@@ -0,0 +1,82 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { FC } from 'react';
+import {
+ EuiCollapsibleNavGroup,
+ EuiIcon,
+ EuiSideNav,
+ EuiSideNavItemType,
+ EuiText,
+} from '@elastic/eui';
+import type { BasePathService, NavigateToUrlFn } from '../../../../types/internal';
+import { navigationStyles as styles } from '../../../styles';
+import { useNavigation as useServices } from '../../../services';
+import { ChromeProjectNavigationNodeEnhanced } from '../types';
+
+const navigationNodeToEuiItem = (
+ item: ChromeProjectNavigationNodeEnhanced,
+ { navigateToUrl, basePath }: { navigateToUrl: NavigateToUrlFn; basePath: BasePathService }
+): EuiSideNavItemType => {
+ const href = item.deepLink?.href;
+ const id = item.path ? item.path.join('.') : item.id;
+
+ return {
+ id,
+ name: item.title,
+ onClick:
+ href !== undefined
+ ? (event: React.MouseEvent) => {
+ event.preventDefault();
+ navigateToUrl(basePath.prepend(href!));
+ }
+ : undefined,
+ href,
+ renderItem: item.renderItem,
+ items: item.children?.map((_item) =>
+ navigationNodeToEuiItem(_item, { navigateToUrl, basePath })
+ ),
+ ['data-test-subj']: `nav-item-${id}`,
+ ...(item.icon && {
+ icon: ,
+ }),
+ };
+};
+
+interface Props {
+ navNode: ChromeProjectNavigationNodeEnhanced;
+ items?: ChromeProjectNavigationNodeEnhanced[];
+ defaultIsCollapsed?: boolean;
+}
+
+export const NavigationSectionUI: FC = ({
+ navNode,
+ items = [],
+ defaultIsCollapsed = true,
+}) => {
+ const { id, title, icon } = navNode;
+ const { navigateToUrl, basePath } = useServices();
+
+ return (
+
+
+ navigationNodeToEuiItem(item, { navigateToUrl, basePath }))}
+ css={styles.euiSideNavItems}
+ />
+
+
+ );
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_ui.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_ui.tsx
new file mode 100644
index 0000000000000..7c754b3a9ce76
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_ui.tsx
@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { EuiCollapsibleNavGroup, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui';
+import React, { FC } from 'react';
+import { NavHeader } from './navigation_header';
+
+interface Props {
+ homeRef: string;
+ unstyled?: boolean;
+ footerChildren?: React.ReactNode;
+}
+
+export const NavigationUI: FC = ({ children, unstyled, footerChildren, homeRef }) => {
+ const { euiTheme } = useEuiTheme();
+
+ return (
+ <>
+
+
+
+
+ {unstyled ? (
+ <>{children}>
+ ) : (
+
+ {children}
+
+ {footerChildren && {footerChildren}}
+
+ )}
+ >
+ );
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/recently_accessed.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/recently_accessed.tsx
new file mode 100644
index 0000000000000..0c5b0b0ead1fe
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/recently_accessed.tsx
@@ -0,0 +1,64 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { EuiCollapsibleNavGroup, EuiSideNav, EuiSideNavItemType } from '@elastic/eui';
+import React, { FC } from 'react';
+import useObservable from 'react-use/lib/useObservable';
+import type { Observable } from 'rxjs';
+
+import { RecentItem } from '../../../../types/internal';
+import { useNavigation as useServices } from '../../../services';
+import { navigationStyles as styles } from '../../../styles';
+
+import { getI18nStrings } from '../../i18n_strings';
+
+export interface Props {
+ recentlyAccessed$?: Observable;
+ /**
+ * If true, the recently accessed list will be collapsed by default.
+ * @default false
+ */
+ defaultIsCollapsed?: boolean;
+}
+
+export const RecentlyAccessed: FC = ({
+ recentlyAccessed$: recentlyAccessedProp$,
+ defaultIsCollapsed = false,
+}) => {
+ const strings = getI18nStrings();
+ const { recentlyAccessed$ } = useServices();
+ const recentlyAccessed = useObservable(recentlyAccessedProp$ ?? recentlyAccessed$, []);
+
+ if (recentlyAccessed.length === 0) {
+ return null;
+ }
+
+ const navItems: Array> = [
+ {
+ name: '', // no list header title
+ id: 'recents_root',
+ items: recentlyAccessed.map(({ id, label, link }) => ({
+ id,
+ name: label,
+ href: link,
+ })),
+ },
+ ];
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.helpers.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.helpers.ts
new file mode 100644
index 0000000000000..2b5f1a85e5d56
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.helpers.ts
@@ -0,0 +1,449 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+/**
+ * This is the default navigation tree that is added to a project
+ * when only a project navigation tree is provided.
+ * NOTE: This will have to be updated once we add the deep link ids as each of the node
+ * will contain the deep link information.
+ */
+export const defaultNavigationTree = [
+ {
+ type: 'navGroup',
+ id: 'sharedux:analytics',
+ title: 'Data exploration',
+ icon: 'stats',
+ path: ['sharedux:analytics'],
+ children: [
+ {
+ id: 'root',
+ path: ['sharedux:analytics', 'root'],
+ title: '',
+ children: [
+ {
+ id: 'discover',
+ title: 'Discover',
+ path: ['sharedux:analytics', 'root', 'discover'],
+ },
+ {
+ id: 'dashboard',
+ title: 'Dashboard',
+ path: ['sharedux:analytics', 'root', 'dashboard'],
+ },
+ {
+ id: 'visualize',
+ title: 'Visualize library',
+ path: ['sharedux:analytics', 'root', 'visualize'],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'navGroup',
+ id: 'sharedux:ml',
+ title: 'Machine learning',
+ icon: 'indexMapping',
+ path: ['sharedux:ml'],
+ children: [
+ {
+ title: '',
+ id: 'root',
+ path: ['sharedux:ml', 'root'],
+ children: [
+ {
+ title: 'Overview',
+ id: 'overview',
+ path: ['sharedux:ml', 'root', 'overview'],
+ },
+ {
+ title: 'Notifications',
+ id: 'notifications',
+ path: ['sharedux:ml', 'root', 'notifications'],
+ },
+ ],
+ },
+ {
+ title: 'Anomaly detection',
+ id: 'anomaly_detection',
+ path: ['sharedux:ml', 'anomaly_detection'],
+ children: [
+ {
+ title: 'Jobs',
+ id: 'jobs',
+ path: ['sharedux:ml', 'anomaly_detection', 'jobs'],
+ },
+ {
+ title: 'Anomaly explorer',
+ id: 'explorer',
+ path: ['sharedux:ml', 'anomaly_detection', 'explorer'],
+ },
+ {
+ title: 'Single metric viewer',
+ id: 'single_metric_viewer',
+ path: ['sharedux:ml', 'anomaly_detection', 'single_metric_viewer'],
+ },
+ {
+ title: 'Settings',
+ id: 'settings',
+ path: ['sharedux:ml', 'anomaly_detection', 'settings'],
+ },
+ ],
+ },
+ {
+ title: 'Data frame analytics',
+ id: 'data_frame_analytics',
+ path: ['sharedux:ml', 'data_frame_analytics'],
+ children: [
+ {
+ title: 'Jobs',
+ id: 'jobs',
+ path: ['sharedux:ml', 'data_frame_analytics', 'jobs'],
+ },
+ {
+ title: 'Results explorer',
+ id: 'results_explorer',
+ path: ['sharedux:ml', 'data_frame_analytics', 'results_explorer'],
+ },
+ {
+ title: 'Analytics map',
+ id: 'analytics_map',
+ path: ['sharedux:ml', 'data_frame_analytics', 'analytics_map'],
+ },
+ ],
+ },
+ {
+ title: 'Model management',
+ id: 'model_management',
+ path: ['sharedux:ml', 'model_management'],
+ children: [
+ {
+ title: 'Trained models',
+ id: 'trained_models',
+ path: ['sharedux:ml', 'model_management', 'trained_models'],
+ },
+ {
+ title: 'Nodes',
+ id: 'nodes',
+ path: ['sharedux:ml', 'model_management', 'nodes'],
+ },
+ ],
+ },
+ {
+ title: 'Data visualizer',
+ id: 'data_visualizer',
+ path: ['sharedux:ml', 'data_visualizer'],
+ children: [
+ {
+ title: 'File',
+ id: 'file',
+ path: ['sharedux:ml', 'data_visualizer', 'file'],
+ },
+ {
+ title: 'Data view',
+ id: 'data_view',
+ path: ['sharedux:ml', 'data_visualizer', 'data_view'],
+ },
+ ],
+ },
+ {
+ title: 'AIOps labs',
+ id: 'aiops_labs',
+ path: ['sharedux:ml', 'aiops_labs'],
+ children: [
+ {
+ title: 'Explain log rate spikes',
+ id: 'explain_log_rate_spikes',
+ path: ['sharedux:ml', 'aiops_labs', 'explain_log_rate_spikes'],
+ },
+ {
+ title: 'Log pattern analysis',
+ id: 'log_pattern_analysis',
+ path: ['sharedux:ml', 'aiops_labs', 'log_pattern_analysis'],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'navGroup',
+ title: 'Developer tools',
+ id: 'sharedux:devtools',
+ icon: 'editorCodeBlock',
+ path: ['sharedux:devtools'],
+ children: [
+ {
+ id: 'root',
+ path: ['sharedux:devtools', 'root'],
+ title: '',
+ children: [
+ {
+ title: 'Console',
+ id: 'console',
+ path: ['sharedux:devtools', 'root', 'console'],
+ },
+ {
+ title: 'Search profiler',
+ id: 'search_profiler',
+ path: ['sharedux:devtools', 'root', 'search_profiler'],
+ },
+ {
+ title: 'Grok debugger',
+ id: 'grok_debugger',
+ path: ['sharedux:devtools', 'root', 'grok_debugger'],
+ },
+ {
+ title: 'Painless lab',
+ id: 'painless_lab',
+ path: ['sharedux:devtools', 'root', 'painless_lab'],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'navGroup',
+ id: 'sharedux:management',
+ title: 'Management',
+ icon: 'gear',
+ path: ['sharedux:management'],
+ children: [
+ {
+ title: '',
+ id: 'root',
+ path: ['sharedux:management', 'root'],
+ children: [
+ {
+ title: 'Stack monitoring',
+ id: 'stack_monitoring',
+ path: ['sharedux:management', 'root', 'stack_monitoring'],
+ },
+ ],
+ },
+ {
+ title: 'Integration management',
+ id: 'integration_management',
+ path: ['sharedux:management', 'integration_management'],
+ children: [
+ {
+ title: 'Integrations',
+ id: 'integrations',
+ path: ['sharedux:management', 'integration_management', 'integrations'],
+ },
+ {
+ title: 'Fleet',
+ id: 'fleet',
+ path: ['sharedux:management', 'integration_management', 'fleet'],
+ },
+ {
+ title: 'Osquery',
+ id: 'osquery',
+ path: ['sharedux:management', 'integration_management', 'osquery'],
+ },
+ ],
+ },
+ {
+ title: 'Stack management',
+ id: 'stack_management',
+ path: ['sharedux:management', 'stack_management'],
+ children: [
+ {
+ title: 'Upgrade assistant',
+ id: 'upgrade_assistant',
+ path: ['sharedux:management', 'stack_management', 'upgrade_assistant'],
+ },
+ {
+ title: 'Ingest',
+ id: 'ingest',
+ path: ['sharedux:management', 'stack_management', 'ingest'],
+ children: [
+ {
+ title: 'Ingest pipelines',
+ id: 'ingest_pipelines',
+ path: ['sharedux:management', 'stack_management', 'ingest', 'ingest_pipelines'],
+ },
+ {
+ title: 'Logstash pipelines',
+ id: 'logstash_pipelines',
+ path: ['sharedux:management', 'stack_management', 'ingest', 'logstash_pipelines'],
+ },
+ ],
+ },
+ {
+ title: 'Data',
+ id: 'data',
+ path: ['sharedux:management', 'stack_management', 'data'],
+ children: [
+ {
+ title: 'Index management',
+ id: 'index_management',
+ path: ['sharedux:management', 'stack_management', 'data', 'index_management'],
+ },
+ {
+ title: 'Index lifecycle policies',
+ id: 'index_lifecycle_policies',
+ path: [
+ 'sharedux:management',
+ 'stack_management',
+ 'data',
+ 'index_lifecycle_policies',
+ ],
+ },
+ {
+ title: 'Snapshot and restore',
+ id: 'snapshot_and_restore',
+ path: ['sharedux:management', 'stack_management', 'data', 'snapshot_and_restore'],
+ },
+ {
+ title: 'Rollup jobs',
+ id: 'rollup_jobs',
+ path: ['sharedux:management', 'stack_management', 'data', 'rollup_jobs'],
+ },
+ {
+ title: 'Transforms',
+ id: 'transforms',
+ path: ['sharedux:management', 'stack_management', 'data', 'transforms'],
+ },
+ {
+ title: 'Cross-cluster replication',
+ id: 'cross_cluster_replication',
+ path: [
+ 'sharedux:management',
+ 'stack_management',
+ 'data',
+ 'cross_cluster_replication',
+ ],
+ },
+ {
+ title: 'Remote clusters',
+ id: 'remote_clusters',
+ path: ['sharedux:management', 'stack_management', 'data', 'remote_clusters'],
+ },
+ ],
+ },
+ {
+ title: 'Alerts and insights',
+ id: 'alerts_and_insights',
+ path: ['sharedux:management', 'stack_management', 'alerts_and_insights'],
+ children: [
+ {
+ title: 'Rules',
+ id: 'rules',
+ path: ['sharedux:management', 'stack_management', 'alerts_and_insights', 'rules'],
+ },
+ {
+ title: 'Cases',
+ id: 'cases',
+ path: ['sharedux:management', 'stack_management', 'alerts_and_insights', 'cases'],
+ },
+ {
+ title: 'Connectors',
+ id: 'connectors',
+ path: [
+ 'sharedux:management',
+ 'stack_management',
+ 'alerts_and_insights',
+ 'connectors',
+ ],
+ },
+ {
+ title: 'Reporting',
+ id: 'reporting',
+ path: [
+ 'sharedux:management',
+ 'stack_management',
+ 'alerts_and_insights',
+ 'reporting',
+ ],
+ },
+ {
+ title: 'Machine learning',
+ id: 'machine_learning',
+ path: [
+ 'sharedux:management',
+ 'stack_management',
+ 'alerts_and_insights',
+ 'machine_learning',
+ ],
+ },
+ {
+ title: 'Watcher',
+ id: 'watcher',
+ path: ['sharedux:management', 'stack_management', 'alerts_and_insights', 'watcher'],
+ },
+ ],
+ },
+ {
+ title: 'Security',
+ id: 'security',
+ path: ['sharedux:management', 'stack_management', 'security'],
+ children: [
+ {
+ title: 'Users',
+ id: 'users',
+ path: ['sharedux:management', 'stack_management', 'security', 'users'],
+ },
+ {
+ title: 'Roles',
+ id: 'roles',
+ path: ['sharedux:management', 'stack_management', 'security', 'roles'],
+ },
+ {
+ title: 'Role mappings',
+ id: 'role_mappings',
+ path: ['sharedux:management', 'stack_management', 'security', 'role_mappings'],
+ },
+ {
+ title: 'API keys',
+ id: 'api_keys',
+ path: ['sharedux:management', 'stack_management', 'security', 'api_keys'],
+ },
+ ],
+ },
+ {
+ title: 'Kibana',
+ id: 'kibana',
+ path: ['sharedux:management', 'stack_management', 'kibana'],
+ children: [
+ {
+ title: 'Data view',
+ id: 'data_views',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'data_views'],
+ },
+ {
+ title: 'Saved objects',
+ id: 'saved_objects',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'saved_objects'],
+ },
+ {
+ title: 'Tags',
+ id: 'tags',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'tags'],
+ },
+ {
+ title: 'Search sessions',
+ id: 'search_sessions',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'search_sessions'],
+ },
+ {
+ title: 'Spaces',
+ id: 'spaces',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'spaces'],
+ },
+ {
+ title: 'Advanced settings',
+ id: 'advanced_settings',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'advanced_settings'],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+];
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.tsx
index 89ef5c8017734..496f693a82ee4 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.tsx
@@ -9,21 +9,142 @@
import React from 'react';
import { render } from '@testing-library/react';
import { type Observable, of } from 'rxjs';
-import type { ChromeNavLink, ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
+import type { ChromeNavLink } from '@kbn/core-chrome-browser';
import { getServicesMock } from '../../../mocks/src/jest';
import { NavigationProvider } from '../../services';
import { DefaultNavigation } from './default_navigation';
+import type { ProjectNavigationTreeDefinition, RootNavigationItemDefinition } from './types';
+import { defaultNavigationTree } from './default_navigation.test.helpers';
+
+const defaultProps = {
+ homeRef: 'https://elastic.co',
+};
describe('', () => {
const services = getServicesMock();
- describe('builds the navigation tree', () => {
- test('should read the title from config or deeplink', async () => {
+ describe('builds custom navigation tree', () => {
+ test('render reference UI and build the navigation tree', async () => {
+ const onProjectNavigationChange = jest.fn();
+
+ const navigationBody: RootNavigationItemDefinition[] = [
+ {
+ type: 'navGroup',
+ id: 'group1',
+ children: [
+ {
+ id: 'item1',
+ title: 'Item 1',
+ },
+ {
+ id: 'item2',
+ title: 'Item 2',
+ },
+ {
+ id: 'group1A',
+ title: 'Group1A',
+ children: [
+ {
+ id: 'item1',
+ title: 'Group 1A Item 1',
+ },
+ {
+ id: 'group1A_1',
+ title: 'Group1A_1',
+ children: [
+ {
+ id: 'item1',
+ title: 'Group 1A_1 Item 1',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ];
+
+ const { findByTestId } = render(
+
+
+
+ );
+
+ expect(await findByTestId('nav-item-group1.item1')).toBeVisible();
+ expect(await findByTestId('nav-item-group1.item2')).toBeVisible();
+ expect(await findByTestId('nav-item-group1.group1A')).toBeVisible();
+ expect(await findByTestId('nav-item-group1.group1A.item1')).toBeVisible();
+ expect(await findByTestId('nav-item-group1.group1A.group1A_1')).toBeVisible();
+
+ // Click the last group to expand and show the last depth
+ (await findByTestId('nav-item-group1.group1A.group1A_1')).click();
+
+ expect(await findByTestId('nav-item-group1.group1A.group1A_1.item1')).toBeVisible();
+
+ expect(onProjectNavigationChange).toHaveBeenCalled();
+ const lastCall =
+ onProjectNavigationChange.mock.calls[onProjectNavigationChange.mock.calls.length - 1];
+ const [navTreeGenerated] = lastCall;
+
+ expect(navTreeGenerated).toEqual({
+ homeRef: 'https://elastic.co',
+ navigationTree: [
+ {
+ id: 'group1',
+ path: ['group1'],
+ title: '',
+ children: [
+ {
+ id: 'item1',
+ title: 'Item 1',
+ path: ['group1', 'item1'],
+ },
+ {
+ id: 'item2',
+ title: 'Item 2',
+ path: ['group1', 'item2'],
+ },
+ {
+ id: 'group1A',
+ title: 'Group1A',
+ path: ['group1', 'group1A'],
+ children: [
+ {
+ id: 'item1',
+ title: 'Group 1A Item 1',
+ path: ['group1', 'group1A', 'item1'],
+ },
+ {
+ id: 'group1A_1',
+ title: 'Group1A_1',
+ path: ['group1', 'group1A', 'group1A_1'],
+ children: [
+ {
+ id: 'item1',
+ title: 'Group 1A_1 Item 1',
+ path: ['group1', 'group1A', 'group1A_1', 'item1'],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ });
+ });
+
+ test('should read the title from deeplink', async () => {
const navLinks$: Observable = of([
{
- id: 'item2',
- title: 'Title from deeplink!',
+ id: 'item1',
+ title: 'Title from deeplink',
baseUrl: '',
url: '',
href: '',
@@ -32,23 +153,31 @@ describe('', () => {
const onProjectNavigationChange = jest.fn();
- const navTreeConfig: ChromeProjectNavigationNode[] = [
+ const navigationBody: RootNavigationItemDefinition[] = [
{
- id: 'item1',
- title: 'Item 1',
- },
- {
- id: 'item2-a',
- link: 'item2', // Title from deeplink
- },
- {
- id: 'item2-b',
- link: 'item2',
- title: 'Override the deeplink with props',
- },
- {
- link: 'disabled',
- title: 'Should NOT be there',
+ type: 'navGroup',
+ id: 'root',
+ children: [
+ {
+ id: 'group1',
+ children: [
+ {
+ id: 'item1',
+ link: 'item1', // Title from deeplink
+ },
+ {
+ id: 'item2',
+ link: 'item1', // Overwrite title from deeplink
+ title: 'Overwrite deeplink title',
+ },
+ {
+ id: 'item3',
+ link: 'unknown', // Unknown deeplink
+ title: 'Should not be rendered',
+ },
+ ],
+ },
+ ],
},
];
@@ -58,7 +187,12 @@ describe('', () => {
navLinks$={navLinks$}
onProjectNavigationChange={onProjectNavigationChange}
>
-
+
);
@@ -68,38 +202,119 @@ describe('', () => {
const [navTreeGenerated] = lastCall;
expect(navTreeGenerated).toEqual({
+ homeRef: 'https://elastic.co',
navigationTree: [
{
- id: 'item1',
- title: 'Item 1',
- },
- {
- id: 'item2-a',
- title: 'Title from deeplink!',
- deepLink: {
- id: 'item2',
- title: 'Title from deeplink!',
- baseUrl: '',
- url: '',
- href: '',
- },
- },
- {
- id: 'item2-b',
- title: 'Override the deeplink with props',
- deepLink: {
- id: 'item2',
- title: 'Title from deeplink!',
- baseUrl: '',
- url: '',
- href: '',
- },
+ id: 'root',
+ path: ['root'],
+ title: '',
+ children: [
+ {
+ id: 'group1',
+ path: ['root', 'group1'],
+ title: '',
+ children: [
+ {
+ id: 'item1',
+ path: ['root', 'group1', 'item1'],
+ title: 'Title from deeplink',
+ deepLink: {
+ id: 'item1',
+ title: 'Title from deeplink',
+ baseUrl: '',
+ url: '',
+ href: '',
+ },
+ },
+ {
+ id: 'item2',
+ title: 'Overwrite deeplink title',
+ path: ['root', 'group1', 'item2'],
+ deepLink: {
+ id: 'item1',
+ title: 'Title from deeplink',
+ baseUrl: '',
+ url: '',
+ href: '',
+ },
+ },
+ ],
+ },
+ ],
},
],
});
});
- test('should render any level of depth', async () => {
+ test('should render cloud link', async () => {
+ const navigationBody: RootNavigationItemDefinition[] = [
+ {
+ type: 'cloudLink',
+ preset: 'deployments',
+ },
+ {
+ type: 'cloudLink',
+ preset: 'projects',
+ },
+ {
+ type: 'cloudLink',
+ href: 'https://foo.com',
+ icon: 'myIcon',
+ title: 'Custom link',
+ },
+ ];
+
+ const { findByTestId } = render(
+
+
+
+ );
+
+ expect(await findByTestId('nav-header-link-to-projects')).toBeVisible();
+ expect(await findByTestId('nav-header-link-to-deployments')).toBeVisible();
+ expect(await findByTestId('nav-header-link-to-cloud')).toBeVisible();
+ expect(await (await findByTestId('nav-header-link-to-cloud')).textContent).toBe(
+ 'Custom link'
+ );
+ });
+
+ test('should render recently accessed items', async () => {
+ const recentlyAccessed$ = of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]);
+
+ const navigationBody: RootNavigationItemDefinition[] = [
+ {
+ type: 'recentlyAccessed',
+ },
+ ];
+
+ const { findByTestId } = render(
+
+
+
+ );
+
+ expect(await findByTestId('nav-bucket-recentlyAccessed')).toBeVisible();
+ expect(await (await findByTestId('nav-bucket-recentlyAccessed')).textContent).toBe(
+ 'RecentThis is an exampleAnother example'
+ );
+ });
+ });
+
+ describe('builds the full navigation tree when only custom project is provided', () => {
+ test('reading the title from config or deeplink', async () => {
const navLinks$: Observable = of([
{
id: 'item2',
@@ -112,25 +327,28 @@ describe('', () => {
const onProjectNavigationChange = jest.fn();
- const navTreeConfig: ChromeProjectNavigationNode[] = [
+ // Custom project navigation tree definition
+ const projectNavigationTree: ProjectNavigationTreeDefinition = [
{
- id: 'item1',
- title: 'Item 1',
+ id: 'group1',
+ title: 'Group 1',
children: [
{
id: 'item1',
title: 'Item 1',
- children: [
- {
- link: 'item2', // Title from deeplink
- children: [
- {
- id: 'item1',
- title: 'Item 1',
- },
- ],
- },
- ],
+ },
+ {
+ id: 'item2',
+ link: 'item2', // Title from deeplink
+ },
+ {
+ id: 'item3',
+ link: 'item2',
+ title: 'Deeplink title overriden', // Override title from deeplink
+ },
+ {
+ link: 'disabled',
+ title: 'Should NOT be there',
},
],
},
@@ -142,7 +360,7 @@ describe('', () => {
navLinks$={navLinks$}
onProjectNavigationChange={onProjectNavigationChange}
>
-
+
);
@@ -152,43 +370,48 @@ describe('', () => {
const [navTreeGenerated] = lastCall;
expect(navTreeGenerated).toEqual({
+ homeRef: 'https://elastic.co',
navigationTree: [
{
- id: 'item1',
- title: 'Item 1',
+ id: 'group1',
+ title: 'Group 1',
+ path: ['group1'],
children: [
{
id: 'item1',
title: 'Item 1',
- children: [
- {
- id: 'item2',
- title: 'Title from deeplink!',
- deepLink: {
- id: 'item2',
- title: 'Title from deeplink!',
- baseUrl: '',
- url: '',
- href: '',
- },
- children: [
- {
- id: 'item1',
- title: 'Item 1',
- },
- ],
- },
- ],
+ path: ['group1', 'item1'],
+ },
+ {
+ id: 'item2',
+ path: ['group1', 'item2'],
+ title: 'Title from deeplink!',
+ deepLink: {
+ id: 'item2',
+ title: 'Title from deeplink!',
+ baseUrl: '',
+ url: '',
+ href: '',
+ },
+ },
+ {
+ id: 'item3',
+ title: 'Deeplink title overriden',
+ path: ['group1', 'item3'],
+ deepLink: {
+ id: 'item2',
+ title: 'Title from deeplink!',
+ baseUrl: '',
+ url: '',
+ href: '',
+ },
},
],
},
+ // The default navigation tree is added at the end
+ ...defaultNavigationTree.map(({ type, ...rest }) => rest),
],
});
});
-
- test.skip('does not render in the UI the nodes that points to unexisting deeplinks', async () => {
- // TODO: This test will be added when we'll have the UI and be able to add
- // data-test-subj to all the nodes with their paths
- });
});
});
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.tsx
index b3f4145d52d54..e1a34b5e5092d 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.tsx
@@ -6,36 +6,103 @@
* Side Public License, v 1.
*/
-import React, { FC, useCallback, useState } from 'react';
-import { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
+import React, { FC, useCallback } from 'react';
import { Navigation } from './components';
+import type {
+ GroupDefinition,
+ NavigationGroupPreset,
+ NavigationTreeDefinition,
+ NodeDefinition,
+ ProjectNavigationDefinition,
+ ProjectNavigationTreeDefinition,
+ RootNavigationItemDefinition,
+} from './types';
+import { CloudLink } from './components/cloud_link';
+import { RecentlyAccessed } from './components/recently_accessed';
+import { NavigationFooter } from './components/navigation_footer';
+import { getPresets } from './nav_tree_presets';
-interface Props {
- navTree: ChromeProjectNavigationNode[];
-}
+type NodeDefinitionWithPreset = NodeDefinition & { preset?: NavigationGroupPreset };
-export const DefaultNavigation: FC = ({ navTree }) => {
- // Temp logic to demo removing items from the tree
- const [removedItems, setRemovedItems] = useState>(new Set());
+const isRootNavigationItemDefinition = (
+ item: RootNavigationItemDefinition | NodeDefinitionWithPreset
+): item is RootNavigationItemDefinition => {
+ // Only RootNavigationItemDefinition has a "type" property
+ return (item as RootNavigationItemDefinition).type !== undefined;
+};
+
+const getDefaultNavigationTree = (
+ projectDefinition: ProjectNavigationTreeDefinition
+): NavigationTreeDefinition => {
+ return {
+ body: [
+ {
+ type: 'cloudLink',
+ preset: 'deployments',
+ },
+ {
+ type: 'recentlyAccessed',
+ },
+ ...projectDefinition.map((def) => ({ ...def, type: 'navGroup' as const })),
+ {
+ type: 'navGroup',
+ ...getPresets('analytics'),
+ },
+ {
+ type: 'navGroup',
+ ...getPresets('ml'),
+ },
+ ],
+ footer: [
+ {
+ type: 'navGroup',
+ ...getPresets('devtools'),
+ },
+ {
+ type: 'navGroup',
+ ...getPresets('management'),
+ },
+ ],
+ };
+};
- const onRemove = useCallback((path: string[]) => {
- setRemovedItems((prevItems) => {
- const newItems = new Set(prevItems);
- newItems.add(path.join('.'));
- return newItems;
- });
- }, []);
+let idCounter = 0;
+
+export const DefaultNavigation: FC = ({
+ homeRef,
+ projectNavigationTree,
+ navigationTree,
+}) => {
+ if (!navigationTree && !projectNavigationTree) {
+ throw new Error('One of navigationTree or projectNavigationTree must be defined');
+ }
+
+ const navigationDefinition = !navigationTree
+ ? getDefaultNavigationTree(projectNavigationTree!)
+ : navigationTree!;
const renderItems = useCallback(
- (items: ChromeProjectNavigationNode[], path: string[] = []) => {
- const filtered = items.filter(({ id: _id, link = '' }) => {
- const id = _id ?? link;
- const itemPath = (id ? [...path, id] : path).join('.');
- return !removedItems.has(itemPath);
- });
+ (
+ items: Array = [],
+ path: string[] = []
+ ) => {
+ return items.map((item) => {
+ const isRootNavigationItem = isRootNavigationItemDefinition(item);
+ if (isRootNavigationItem) {
+ if (item.type === 'cloudLink') {
+ return ;
+ }
+
+ if (item.type === 'recentlyAccessed') {
+ return ;
+ }
+ }
+
+ if (item.preset) {
+ return ;
+ }
- return filtered.map((item) => {
const id = item.id ?? item.link;
if (!id) {
@@ -44,44 +111,32 @@ export const DefaultNavigation: FC = ({ navTree }) => {
);
}
+ const { type, ...rest } = item as GroupDefinition;
+
return (
- {item.children ? (
- onRemove([...path, id])}
- >
- {renderItems(item.children, [...path, id])}
+ {rest.children ? (
+
+ {renderItems(rest.children, [...path, id])}
) : (
- onRemove([...path, id])}
- />
+
)}
);
});
},
- [removedItems, onRemove]
+ []
);
- const filteredNavTree = navTree.filter(({ id: _id, link }) => {
- const id = _id ?? link;
- return !removedItems.has(id ?? '');
- });
-
return (
- {
- onRemove([id]);
- }}
- >
- {renderItems(filteredNavTree)}
+
+ <>
+ {renderItems(navigationDefinition.body)}
+ {navigationDefinition.footer && (
+ {renderItems(navigationDefinition.footer)}
+ )}
+ >
);
};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/hooks/index.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/hooks/index.ts
new file mode 100644
index 0000000000000..631ad5f590ce4
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/hooks/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { useInitNavNode } from './use_init_navnode';
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/hooks/use_init_navnode.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/hooks/use_init_navnode.ts
new file mode 100644
index 0000000000000..2d3e332b7b890
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/hooks/use_init_navnode.ts
@@ -0,0 +1,221 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { ChromeNavLink, ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import useObservable from 'react-use/lib/useObservable';
+
+import { useNavigation as useNavigationServices } from '../../../services';
+import {
+ ChromeProjectNavigationNodeEnhanced,
+ NodeProps,
+ NodePropsEnhanced,
+ RegisterFunction,
+ UnRegisterFunction,
+} from '../types';
+import { useRegisterTreeNode } from './use_register_tree_node';
+
+function getIdFromNavigationNode({ id: _id, link, title }: NodeProps): string {
+ const id = _id ?? link;
+
+ if (!id) {
+ throw new Error(`Id or link prop missing for navigation item [${title}]`);
+ }
+
+ return id;
+}
+
+function isNodeVisible({ link, deepLink }: { link?: string; deepLink?: ChromeNavLink }) {
+ if (link && !deepLink) {
+ // If a link is provided, but no deepLink is found, don't render anything
+ return false;
+ }
+ return true;
+}
+
+function createInternalNavNode(
+ id: string,
+ _navNode: NodePropsEnhanced,
+ deepLinks: Readonly,
+ path: string[] | null
+): ChromeProjectNavigationNodeEnhanced | null {
+ const { children, link, ...navNode } = _navNode;
+ const deepLink = deepLinks.find((dl) => dl.id === link);
+ const isVisible = isNodeVisible({ link, deepLink });
+
+ const titleFromDeepLinkOrChildren = typeof children === 'string' ? children : deepLink?.title;
+ const title = navNode.title ?? titleFromDeepLinkOrChildren;
+
+ if (!isVisible) {
+ return null;
+ }
+
+ return {
+ ...navNode,
+ id,
+ path: path ?? [id],
+ title: title ?? '',
+ deepLink,
+ };
+}
+
+export const useInitNavNode = (node: NodePropsEnhanced) => {
+ /**
+ * Map of children nodes
+ */
+ const [childrenNodes, setChildrenNodes] = useState<
+ Record
+ >({});
+
+ const isMounted = useRef(false);
+
+ /**
+ * Flag to indicate if the current node has been registered
+ */
+ const isRegistered = useRef(false);
+
+ /**
+ * Reference to the unregister function
+ */
+ const unregisterRef = useRef();
+
+ /**
+ * Map to keep track of the order of the children when they mount.
+ * This allows us to keep in sync the nav tree sent to the Chrome service
+ * with the order of the DOM elements
+ */
+ const orderChildrenRef = useRef>({});
+
+ /**
+ * Index to keep track of the order of the children when they mount.
+ */
+ const idx = useRef(0);
+
+ /**
+ * The current node path, including all of its parents. We'll use it to match it against
+ * the list of active routes based on current URL location (passed by the Chrome service)
+ */
+ const [nodePath, setNodePath] = useState(null);
+
+ /**
+ * Whenever a child node is registered, we need to re-register the current node
+ * on the parent. This state keeps track when child node register.
+ */
+ const [childrenNodesUpdated, setChildrenNodesUpdated] = useState([]);
+
+ const { navLinks$ } = useNavigationServices();
+ const deepLinks = useObservable(navLinks$, []);
+ const { register: registerNodeOnParent } = useRegisterTreeNode();
+
+ const id = getIdFromNavigationNode(node);
+
+ const internalNavNode = useMemo(
+ () => createInternalNavNode(id, node, deepLinks, nodePath),
+ [node, id, deepLinks, nodePath]
+ );
+
+ // Register the node on the parent whenever its properties change or whenever
+ // a child node is registered.
+ const register = useCallback(() => {
+ if (!internalNavNode) {
+ return;
+ }
+
+ if (!isRegistered.current || childrenNodesUpdated.length > 0) {
+ const children = Object.values(childrenNodes).sort((a, b) => {
+ const aOrder = orderChildrenRef.current[a.id];
+ const bOrder = orderChildrenRef.current[b.id];
+ return aOrder - bOrder;
+ });
+
+ const { unregister, path } = registerNodeOnParent({
+ ...internalNavNode,
+ children: children.length ? children : undefined,
+ });
+
+ setNodePath(path);
+ setChildrenNodesUpdated([]);
+
+ unregisterRef.current = unregister;
+ isRegistered.current = true;
+ }
+ }, [internalNavNode, childrenNodesUpdated.length, childrenNodes, registerNodeOnParent]);
+
+ // Un-register from the parent. This will happen when the node is unmounted or if the deeplink
+ // is not active anymore.
+ const unregister = useCallback(() => {
+ isRegistered.current = false;
+ if (unregisterRef.current) {
+ unregisterRef.current(id);
+ unregisterRef.current = undefined;
+ }
+ }, [id]);
+
+ const registerChildNode = useCallback(
+ (childNode) => {
+ const childPath = nodePath ? [...nodePath, childNode.id] : [];
+
+ setChildrenNodes((prev) => {
+ return {
+ ...prev,
+ [childNode.id]: {
+ ...childNode,
+ path: childPath,
+ },
+ };
+ });
+
+ orderChildrenRef.current[childNode.id] = idx.current++;
+ setChildrenNodesUpdated((prev) => [...prev, childNode.id]);
+
+ return {
+ unregister: (childId: string) => {
+ setChildrenNodes((prev) => {
+ const updatedItems = { ...prev };
+ delete updatedItems[childId];
+ return updatedItems;
+ });
+ },
+ path: childPath,
+ };
+ },
+ [nodePath]
+ );
+
+ /** Register when mounting and whenever the internal nav node changes */
+ useEffect(() => {
+ if (!isMounted.current) {
+ return;
+ }
+
+ if (internalNavNode) {
+ register();
+ } else {
+ unregister();
+ }
+ }, [unregister, register, internalNavNode]);
+
+ /** Unregister when unmounting */
+ useEffect(() => {
+ isMounted.current = true;
+ return () => {
+ isMounted.current = false;
+ unregister();
+ };
+ }, [unregister]);
+
+ return useMemo(
+ () => ({
+ navNode: internalNavNode,
+ path: nodePath,
+ registerChildNode,
+ childrenNodes,
+ }),
+ [internalNavNode, registerChildNode, nodePath, childrenNodes]
+ );
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/use_register_tree_node.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/hooks/use_register_tree_node.ts
similarity index 75%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/use_register_tree_node.ts
rename to packages/shared-ux/chrome/navigation/src/ui/v2/hooks/use_register_tree_node.ts
index 4e5cf26392c93..5ad690b2de2e1 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/use_register_tree_node.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/hooks/use_register_tree_node.ts
@@ -8,8 +8,8 @@
import { useMemo } from 'react';
-import { useNavigation } from './components/navigation';
-import { useNavigationGroup } from './components/navigation_group';
+import { useNavigation } from '../components/navigation';
+import { useNavigationGroup } from '../components/navigation_group';
/**
* Helper hook that will proxy the correct "register" handler.
@@ -17,8 +17,8 @@ import { useNavigationGroup } from './components/navigation_group';
*/
export const useRegisterTreeNode = () => {
const root = useNavigation();
- const parent = useNavigationGroup(false);
- const register = parent ? parent.register : root.register;
+ const group = useNavigationGroup(false);
+ const register = group ? group.register : root.register;
return useMemo(
() => ({
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/analytics.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/analytics.ts
new file mode 100644
index 0000000000000..119b47cd5db3a
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/analytics.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { NodeDefinitionWithChildren } from '.';
+
+export type ID = 'sharedux:analytics' | 'root' | 'discover' | 'dashboard' | 'visualize';
+
+export const analytics: NodeDefinitionWithChildren = {
+ // Make sure we have a unique id otherwise it might override a custom id from the project
+ id: 'sharedux:analytics',
+ title: 'Data exploration',
+ icon: 'stats',
+ children: [
+ {
+ id: 'root',
+ children: [
+ {
+ id: 'discover',
+ title: 'Discover',
+ },
+ {
+ id: 'dashboard',
+ title: 'Dashboard',
+ },
+ {
+ id: 'visualize',
+ title: 'Visualize library',
+ },
+ ],
+ },
+ ],
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/devtools.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/devtools.ts
new file mode 100644
index 0000000000000..e0c0dcffcad5f
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/devtools.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { NodeDefinitionWithChildren } from '.';
+
+export type ID =
+ | 'sharedux:devtools'
+ | 'root'
+ | 'console'
+ | 'search_profiler'
+ | 'grok_debugger'
+ | 'painless_lab';
+
+export const devtools: NodeDefinitionWithChildren = {
+ title: 'Developer tools',
+ id: 'sharedux:devtools',
+ icon: 'editorCodeBlock',
+ children: [
+ {
+ id: 'root',
+ children: [
+ {
+ title: 'Console',
+ id: 'console',
+ },
+ {
+ title: 'Search profiler',
+ id: 'search_profiler',
+ },
+ {
+ title: 'Grok debugger',
+ id: 'grok_debugger',
+ },
+ {
+ title: 'Painless lab',
+ id: 'painless_lab',
+ },
+ ],
+ },
+ ],
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/index.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/index.ts
new file mode 100644
index 0000000000000..1cdbdaaa551ea
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/index.ts
@@ -0,0 +1,67 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { cloneDeep } from 'lodash';
+import { NavigationGroupPreset, NodeDefinition } from '../types';
+import { analytics, type ID as AnalyticsID } from './analytics';
+import { devtools, type ID as DevtoolsID } from './devtools';
+import { management, type ID as ManagementID } from './management';
+import { ml, type ID as MlID } from './ml';
+
+export { analytics } from './analytics';
+export { devtools } from './devtools';
+export { ml } from './ml';
+export { management } from './management';
+
+export type NodeDefinitionWithChildren = NodeDefinition & {
+ children: Required>['children'];
+};
+
+export function getPresets(preset: 'devtools'): NodeDefinitionWithChildren;
+export function getPresets(preset: 'management'): NodeDefinitionWithChildren;
+export function getPresets(preset: 'ml'): NodeDefinitionWithChildren;
+export function getPresets(preset: 'analytics'): NodeDefinitionWithChildren;
+export function getPresets(preset: 'all'): {
+ analytics: NodeDefinitionWithChildren;
+ devtools: NodeDefinitionWithChildren;
+ ml: NodeDefinitionWithChildren;
+ management: NodeDefinitionWithChildren;
+};
+export function getPresets(preset: NavigationGroupPreset | 'all'):
+ | NodeDefinitionWithChildren
+ | NodeDefinitionWithChildren
+ | NodeDefinitionWithChildren
+ | NodeDefinitionWithChildren
+ | {
+ analytics: NodeDefinitionWithChildren;
+ devtools: NodeDefinitionWithChildren;
+ ml: NodeDefinitionWithChildren;
+ management: NodeDefinitionWithChildren;
+ } {
+ if (preset === 'all') {
+ return {
+ analytics: cloneDeep(analytics),
+ devtools: cloneDeep(devtools),
+ ml: cloneDeep(ml),
+ management: cloneDeep(management),
+ };
+ }
+
+ switch (preset) {
+ case 'analytics':
+ return cloneDeep(analytics);
+ case 'devtools':
+ return cloneDeep(devtools);
+ case 'ml':
+ return cloneDeep(ml);
+ case 'management':
+ return cloneDeep(management);
+ default:
+ throw new Error(`Unknown preset: ${preset}`);
+ }
+}
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/management.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/management.ts
new file mode 100644
index 0000000000000..3483d0b03ed11
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/management.ts
@@ -0,0 +1,226 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { NodeDefinitionWithChildren } from '.';
+
+export type ID =
+ | 'sharedux:management'
+ | 'root'
+ | 'stack_monitoring'
+ | 'integration_management'
+ | 'integrations'
+ | 'fleet'
+ | 'osquery'
+ | 'stack_management'
+ | 'ingest'
+ | 'ingest_pipelines'
+ | 'logstash_pipelines'
+ | 'data'
+ | 'index_management'
+ | 'index_lifecycle_policies'
+ | 'snapshot_and_restore'
+ | 'rollup_jobs'
+ | 'transforms'
+ | 'cross_cluster_replication'
+ | 'remote_clusters'
+ | 'alerts_and_insights'
+ | 'rules'
+ | 'cases'
+ | 'connectors'
+ | 'reporting'
+ | 'machine_learning'
+ | 'watcher'
+ | 'security'
+ | 'users'
+ | 'roles'
+ | 'role_mappings'
+ | 'api_keys'
+ | 'kibana'
+ | 'data_views'
+ | 'saved_objects'
+ | 'tags'
+ | 'search_sessions'
+ | 'spaces'
+ | 'advanced_settings'
+ | 'upgrade_assistant';
+
+export const management: NodeDefinitionWithChildren = {
+ id: 'sharedux:management',
+ title: 'Management',
+ icon: 'gear',
+ children: [
+ {
+ title: '',
+ id: 'root',
+ children: [
+ {
+ title: 'Stack monitoring',
+ id: 'stack_monitoring',
+ },
+ ],
+ },
+ {
+ title: 'Integration management',
+ id: 'integration_management',
+ children: [
+ {
+ title: 'Integrations',
+ id: 'integrations',
+ },
+ {
+ title: 'Fleet',
+ id: 'fleet',
+ },
+ {
+ title: 'Osquery',
+ id: 'osquery',
+ },
+ ],
+ },
+ {
+ title: 'Stack management',
+ id: 'stack_management',
+ children: [
+ {
+ title: 'Ingest',
+ id: 'ingest',
+ children: [
+ {
+ title: 'Ingest pipelines',
+ id: 'ingest_pipelines',
+ },
+ {
+ title: 'Logstash pipelines',
+ id: 'logstash_pipelines',
+ },
+ ],
+ },
+ {
+ title: 'Data',
+ id: 'data',
+ children: [
+ {
+ title: 'Index management',
+ id: 'index_management',
+ },
+ {
+ title: 'Index lifecycle policies',
+ id: 'index_lifecycle_policies',
+ },
+ {
+ title: 'Snapshot and restore',
+ id: 'snapshot_and_restore',
+ },
+ {
+ title: 'Rollup jobs',
+ id: 'rollup_jobs',
+ },
+ {
+ title: 'Transforms',
+ id: 'transforms',
+ },
+ {
+ title: 'Cross-cluster replication',
+ id: 'cross_cluster_replication',
+ },
+ {
+ title: 'Remote clusters',
+ id: 'remote_clusters',
+ },
+ ],
+ },
+ {
+ title: 'Alerts and insights',
+ id: 'alerts_and_insights',
+ children: [
+ {
+ title: 'Rules',
+ id: 'rules',
+ },
+ {
+ title: 'Cases',
+ id: 'cases',
+ },
+ {
+ title: 'Connectors',
+ id: 'connectors',
+ },
+ {
+ title: 'Reporting',
+ id: 'reporting',
+ },
+ {
+ title: 'Machine learning',
+ id: 'machine_learning',
+ },
+ {
+ title: 'Watcher',
+ id: 'watcher',
+ },
+ ],
+ },
+ {
+ title: 'Security',
+ id: 'security',
+ children: [
+ {
+ title: 'Users',
+ id: 'users',
+ },
+ {
+ title: 'Roles',
+ id: 'roles',
+ },
+ {
+ title: 'Role mappings',
+ id: 'role_mappings',
+ },
+ {
+ title: 'API keys',
+ id: 'api_keys',
+ },
+ ],
+ },
+ {
+ title: 'Kibana',
+ id: 'kibana',
+ children: [
+ {
+ title: 'Data view',
+ id: 'data_views',
+ },
+ {
+ title: 'Saved objects',
+ id: 'saved_objects',
+ },
+ {
+ title: 'Tags',
+ id: 'tags',
+ },
+ {
+ title: 'Search sessions',
+ id: 'search_sessions',
+ },
+ {
+ title: 'Spaces',
+ id: 'spaces',
+ },
+ {
+ title: 'Advanced settings',
+ id: 'advanced_settings',
+ },
+ ],
+ },
+ {
+ title: 'Upgrade assistant',
+ id: 'upgrade_assistant',
+ },
+ ],
+ },
+ ],
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/ml.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/ml.ts
new file mode 100644
index 0000000000000..d11a97827490c
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/ml.ts
@@ -0,0 +1,136 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { NodeDefinitionWithChildren } from '.';
+
+export type ID =
+ | 'sharedux:ml'
+ | 'root'
+ | 'overview'
+ | 'notifications'
+ | 'anomaly_detection'
+ | 'jobs'
+ | 'explorer'
+ | 'single_metric_viewer'
+ | 'settings'
+ | 'data_frame_analytics'
+ | 'results_explorer'
+ | 'analytics_map'
+ | 'model_management'
+ | 'trained_models'
+ | 'nodes'
+ | 'data_visualizer'
+ | 'file'
+ | 'data_view'
+ | 'aiops_labs'
+ | 'explain_log_rate_spikes'
+ | 'log_pattern_analysis';
+
+export const ml: NodeDefinitionWithChildren = {
+ id: 'sharedux:ml',
+ title: 'Machine learning',
+ icon: 'indexMapping',
+ children: [
+ {
+ title: '',
+ id: 'root',
+ children: [
+ {
+ title: 'Overview',
+ id: 'overview',
+ },
+ {
+ title: 'Notifications',
+ id: 'notifications',
+ },
+ ],
+ },
+ {
+ title: 'Anomaly detection',
+ id: 'anomaly_detection',
+ children: [
+ {
+ title: 'Jobs',
+ id: 'jobs',
+ },
+ {
+ title: 'Anomaly explorer',
+ id: 'explorer',
+ },
+ {
+ title: 'Single metric viewer',
+ id: 'single_metric_viewer',
+ },
+ {
+ title: 'Settings',
+ id: 'settings',
+ },
+ ],
+ },
+ {
+ title: 'Data frame analytics',
+ id: 'data_frame_analytics',
+ children: [
+ {
+ title: 'Jobs',
+ id: 'jobs',
+ },
+ {
+ title: 'Results explorer',
+ id: 'results_explorer',
+ },
+ {
+ title: 'Analytics map',
+ id: 'analytics_map',
+ },
+ ],
+ },
+ {
+ title: 'Model management',
+ id: 'model_management',
+ children: [
+ {
+ title: 'Trained models',
+ id: 'trained_models',
+ },
+ {
+ title: 'Nodes',
+ id: 'nodes',
+ },
+ ],
+ },
+ {
+ title: 'Data visualizer',
+ id: 'data_visualizer',
+ children: [
+ {
+ title: 'File',
+ id: 'file',
+ },
+ {
+ title: 'Data view',
+ id: 'data_view',
+ },
+ ],
+ },
+ {
+ title: 'AIOps labs',
+ id: 'aiops_labs',
+ children: [
+ {
+ title: 'Explain log rate spikes',
+ id: 'explain_log_rate_spikes',
+ },
+ {
+ title: 'Log pattern analysis',
+ id: 'log_pattern_analysis',
+ },
+ ],
+ },
+ ],
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/navigation.stories.tsx
index 4aff0251f85c0..987d6d3dda8f9 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/navigation.stories.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/navigation.stories.tsx
@@ -6,172 +6,424 @@
* Side Public License, v 1.
*/
-import React from 'react';
+import React, { FC, useCallback, useState } from 'react';
import { of } from 'rxjs';
import { ComponentMeta } from '@storybook/react';
import { action } from '@storybook/addon-actions';
-import { ChromeNavLink, ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
+import type { ChromeNavLink } from '@kbn/core-chrome-browser';
+import {
+ EuiButton,
+ EuiButtonIcon,
+ EuiCollapsibleNav,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLink,
+ EuiText,
+ EuiThemeProvider,
+ EuiTitle,
+} from '@elastic/eui';
+import { css } from '@emotion/react';
import { NavigationStorybookMock } from '../../../mocks';
import mdx from '../../../README.mdx';
import { NavigationProvider } from '../../services';
import { DefaultNavigation } from './default_navigation';
import type { ChromeNavigationViewModel, NavigationServices } from '../../../types';
import { Navigation } from './components';
+import { ProjectNavigationDefinition } from './types';
+import { getPresets } from './nav_tree_presets';
const storybookMock = new NavigationStorybookMock();
-const navTree: ChromeProjectNavigationNode[] = [
- {
- id: 'group1',
- title: 'Group 1',
- children: [
+const SIZE_OPEN = 248;
+const SIZE_CLOSED = 40;
+
+const NavigationWrapper: FC = ({ children }) => {
+ const [isOpen, setIsOpen] = useState(true);
+
+ const collabsibleNavCSS = css`
+ border-inline-end-width: 1,
+ display: flex,
+ flex-direction: row,
+ `;
+
+ const CollapseButton = () => {
+ const buttonCSS = css`
+ margin-left: -32px;
+ position: fixed;
+ z-index: 1000;
+ `;
+ return (
+
+
+
+ );
+ };
+
+ const toggleOpen = useCallback(() => {
+ setIsOpen(!isOpen);
+ }, [isOpen, setIsOpen]);
+
+ return (
+
+ }
+ >
+ {isOpen && children}
+
+
+ );
+};
+
+const baseDeeplink: ChromeNavLink = {
+ id: 'foo',
+ title: 'Title from deep link',
+ href: 'https://elastic.co',
+ url: '',
+ baseUrl: '',
+};
+
+const createDeepLink = (id: string, title: string = baseDeeplink.title) => {
+ return {
+ ...baseDeeplink,
+ id,
+ title,
+ };
+};
+
+const deepLinks: ChromeNavLink[] = [
+ createDeepLink('item1'),
+ createDeepLink('item2', 'Foo'),
+ createDeepLink('group1:item1'),
+ createDeepLink('group1:groupA:groupI:item1'),
+ createDeepLink('group1:groupA', 'Group title from deep link'),
+ createDeepLink('group2', 'Group title from deep link'),
+ createDeepLink('group2:item1'),
+ createDeepLink('group2:item3'),
+];
+
+const simpleNavigationDefinition: ProjectNavigationDefinition = {
+ homeRef: 'https://elastic.co',
+ projectNavigationTree: [
+ {
+ id: 'example_projet',
+ title: 'Example project',
+ icon: 'logoObservability',
+ defaultIsCollapsed: false,
+ children: [
+ {
+ id: 'root',
+ children: [
+ {
+ id: 'item1',
+ title: 'Get started',
+ },
+ {
+ id: 'item2',
+ title: 'Alerts',
+ },
+ {
+ id: 'item3',
+ title: 'Dashboards',
+ },
+ ],
+ },
+ {
+ id: 'group:settings',
+ title: 'Settings',
+ children: [
+ {
+ id: 'logs',
+ title: 'Logs',
+ },
+ {
+ id: 'signals',
+ title: 'Signals',
+ },
+ {
+ id: 'tracing',
+ title: 'Tracing',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+};
+
+export const SimpleObjectDefinition = (args: ChromeNavigationViewModel & NavigationServices) => {
+ const services = storybookMock.getServices({
+ ...args,
+ navLinks$: of(deepLinks),
+ onProjectNavigationChange: (updated) => {
+ action('Update chrome navigation')(JSON.stringify(updated, null, 2));
+ },
+ recentlyAccessed$: of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]),
+ });
+
+ return (
+
+
+
+
+
+ );
+};
+
+const navigationDefinition: ProjectNavigationDefinition = {
+ homeRef: 'https://elastic.co',
+ navigationTree: {
+ body: [
{
- id: 'item1',
- title: 'Group 1: Item 1',
- link: 'group1:item1',
+ type: 'cloudLink',
+ preset: 'deployments',
},
+ // My custom project
{
- id: 'groupA',
- link: 'group1:groupA',
+ type: 'navGroup',
+ id: 'example_projet',
+ title: 'Example project',
+ icon: 'logoObservability',
+ defaultIsCollapsed: false,
children: [
{
- id: 'item1',
- title: 'Group 1 > Group A > Item 1',
- },
- {
- id: 'groupI',
- title: 'Group 1 : Group A : Group I',
+ id: 'root',
children: [
{
id: 'item1',
- title: 'Group 1 > Group A > Group 1 > Item 1',
- link: 'group1:groupA:groupI:item1',
+ title: 'Get started',
},
{
id: 'item2',
- title: 'Group 1 > Group A > Group 1 > Item 2',
+ title: 'Alerts',
+ },
+ {
+ id: 'item3',
+ title: 'Some other node',
},
],
},
{
- id: 'item2',
- title: 'Group 1 > Group A > Item 2',
+ id: 'group:settings',
+ title: 'Settings',
+ children: [
+ {
+ id: 'logs',
+ title: 'Logs',
+ },
+ {
+ id: 'signals',
+ title: 'Signals',
+ },
+ {
+ id: 'tracing',
+ title: 'Tracing',
+ },
+ ],
},
],
},
+ // Add ml
{
- id: 'item3',
- title: 'Group 1: Item 3',
+ type: 'navGroup',
+ preset: 'ml',
},
- ],
- },
- {
- id: 'group2',
- link: 'group2',
- title: 'Group 2',
- children: [
+ // And specific links from analytics
{
- id: 'item1',
- title: 'Group 2: Item 1',
- link: 'group2:item1',
+ type: 'navGroup',
+ ...getPresets('analytics'),
+ title: 'My analytics', // Change the title
+ children: getPresets('analytics').children.map((child) => ({
+ ...child,
+ children: child.children?.filter((item) => {
+ // Hide discover and dashboard
+ return item.id !== 'discover' && item.id !== 'dashboard';
+ }),
+ })),
},
+ ],
+ footer: [
{
- id: 'item2',
- title: 'Group 2: Item 2',
- link: 'group2:item2',
+ type: 'recentlyAccessed',
+ defaultIsCollapsed: true,
+ // Override the default recently accessed items with our own
+ recentlyAccessed$: of([
+ {
+ label: 'My own recent item',
+ id: '1234',
+ link: '/app/example/39859',
+ },
+ {
+ label: 'I also own this',
+ id: '4567',
+ link: '/app/example/39859',
+ },
+ ]),
},
{
- id: 'item3',
- title: 'Group 2: Item 3',
- link: 'group2:item3',
+ type: 'navGroup',
+ ...getPresets('devtools'),
},
],
},
- {
- id: 'item1',
- link: 'item1',
- },
- {
- id: 'item2',
- title: 'Item 2',
- link: 'bad',
- },
- {
- id: 'item3',
- title: "I don't have a 'link' prop",
- },
- {
- id: 'item4',
- title: 'Item 4',
- },
-];
-
-const baseDeeplink: ChromeNavLink = {
- id: 'foo',
- title: 'Title from deep link',
- href: 'https://elastic.co',
- url: '',
- baseUrl: '',
};
-const createDeepLink = (id: string, title: string = baseDeeplink.title) => {
- return {
- ...baseDeeplink,
- id,
- title,
- };
-};
+export const ComplexObjectDefinition = (args: ChromeNavigationViewModel & NavigationServices) => {
+ const services = storybookMock.getServices({
+ ...args,
+ navLinks$: of(deepLinks),
+ onProjectNavigationChange: (updated) => {
+ action('Update chrome navigation')(JSON.stringify(updated, null, 2));
+ },
+ recentlyAccessed$: of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]),
+ });
-const deepLinks: ChromeNavLink[] = [
- createDeepLink('item1'),
- createDeepLink('item2', 'Foo'),
- createDeepLink('group1:item1'),
- createDeepLink('group1:groupA:groupI:item1'),
- createDeepLink('group1:groupA', 'Group title from deep link'),
- createDeepLink('group2', 'Group title from deep link'),
- createDeepLink('group2:item1'),
- createDeepLink('group2:item3'),
-];
+ return (
+
+
+
+
+
+ );
+};
-export const FromObjectConfig = (args: ChromeNavigationViewModel & NavigationServices) => {
+export const WithUIComponents = (args: ChromeNavigationViewModel & NavigationServices) => {
const services = storybookMock.getServices({
...args,
navLinks$: of(deepLinks),
onProjectNavigationChange: (updated) => {
action('Update chrome navigation')(JSON.stringify(updated, null, 2));
},
+ recentlyAccessed$: of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]),
});
return (
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ {(navNode) => {
+ return (
+ {`Render prop: ${navNode.id} - ${navNode.title}`}
+ );
+ }}
+
+
+
+ Title in ReactNode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
-export const FromReactNodes = (args: ChromeNavigationViewModel & NavigationServices) => {
+export const MinimalUIAndCustomCloudLink = (
+ args: ChromeNavigationViewModel & NavigationServices
+) => {
const services = storybookMock.getServices({
...args,
navLinks$: of(deepLinks),
onProjectNavigationChange: (updated) => {
action('Update chrome navigation')(JSON.stringify(updated, null, 2));
},
+ recentlyAccessed$: of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]),
});
return (
-
-
-
-
-
-
-
-
-
- Title from react node
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some children node
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
@@ -183,5 +435,113 @@ export default {
page: mdx,
},
},
- component: FromObjectConfig,
-} as ComponentMeta;
+ component: WithUIComponents,
+} as ComponentMeta;
+
+export const CreativeUI = (args: ChromeNavigationViewModel & NavigationServices) => {
+ const services = storybookMock.getServices({
+ ...args,
+ navLinks$: of(deepLinks),
+ onProjectNavigationChange: (updated) => {
+ action('Update chrome navigation')(JSON.stringify(updated, null, 2));
+ },
+ recentlyAccessed$: of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]),
+ });
+
+ return (
+
+
+
+
+
+
+
+
+ Hello!
+
+
+
+
+
+
+ As you can see there is really no limit in what UI you can create!
+
+
+
+ Have fun!
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/types.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/types.ts
index 1f3c8a094d663..27dcd22654bde 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/types.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/types.ts
@@ -6,27 +6,172 @@
* Side Public License, v 1.
*/
-import { ChromeNavLink, ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
-import { ReactNode } from 'react';
+import type { ReactElement, ReactNode } from 'react';
+import type {
+ ChromeProjectNavigationLink,
+ ChromeProjectNavigationNode,
+} from '@kbn/core-chrome-browser';
-export interface InternalNavigationNode extends Omit {
- id: string;
- title: string;
- deepLink?: ChromeNavLink;
+import type { CloudLinkProps, RecentlyAccessedProps } from './components';
+
+/**
+ * @public
+ *
+ * A navigation node definition with its unique id, title, path in the tree and optional
+ * deep link and children.
+ */
+export interface NodeDefinition {
+ /** Optional id, if not passed a "link" must be provided. */
+ id?: T;
+ /** Optional title. If not provided and a "link" is provided the title will be the Deep link title */
+ title?: string;
+ /** App id or deeplink id */
+ link?: ChromeProjectNavigationLink;
+ /** Optional icon for the navigation node. Note: not all navigation depth will render the icon */
+ icon?: string;
+ /** Optional children of the navigation node */
+ children?: Array>;
+}
+
+/**
+ * @public
+ *
+ * A navigation node definition with its unique id, title, path in the tree and optional deep link.
+ * Those are the props that can be passed to the Navigation.Group and Navigation.Item components.
+ */
+export interface NodeProps extends Omit {
+ /**
+ * Children of the node. For Navigation.Item (only) it allows a function to be set.
+ * This function will receive the ChromeProjectNavigationNode object
+ */
+ children?: ((navNode: ChromeProjectNavigationNode) => ReactNode) | ReactNode;
+}
+
+/**
+ * @private
+ *
+ * Internally we enhance the Props passed to the Navigation.Item component.
+ */
+export interface NodePropsEnhanced extends NodeProps {
+ /**
+ * This function correspond to the same "itemRender" function that can be passed to
+ * the EuiSideNavItemType (see navigation_section_ui.tsx)
+ */
+ renderItem?: () => ReactElement;
}
-export type UnRegisterFunction = () => void;
+export interface ChromeProjectNavigationNodeEnhanced extends ChromeProjectNavigationNode {
+ /**
+ * This function correspond to the same "itemRender" function that can be passed to
+ * the EuiSideNavItemType (see navigation_section_ui.tsx)
+ */
+ renderItem?: () => ReactElement;
+}
+
+/** The preset that can be pass to the NavigationBucket component */
+export type NavigationGroupPreset = 'analytics' | 'devtools' | 'ml' | 'management';
+
+/**
+ * @public
+ *
+ * Definition for the "Recently accessed" section of the side navigation.
+ */
+export interface RecentlyAccessedDefinition extends RecentlyAccessedProps {
+ type: 'recentlyAccessed';
+}
-export type RegisterFunction = (navNode: InternalNavigationNode) => {
+/**
+ * @public
+ *
+ * A cloud link root item definition. Use it to add one or more links to the Cloud console
+ */
+export interface CloudLinkDefinition extends CloudLinkProps {
+ type: 'cloudLink';
+}
+
+/**
+ * @public
+ *
+ * A group root item definition.
+ */
+export interface GroupDefinition extends NodeDefinition {
+ type: 'navGroup';
+ /** Flag to indicate if the group is initially collapsed or not. */
+ defaultIsCollapsed?: boolean;
+ children?: NodeDefinition[];
+ preset?: NavigationGroupPreset;
+}
+
+/**
+ * @public
+ *
+ * The navigation definition for a root item in the side navigation.
+ */
+export type RootNavigationItemDefinition =
+ | RecentlyAccessedDefinition
+ | CloudLinkDefinition
+ | GroupDefinition;
+
+export type ProjectNavigationTreeDefinition = Array>;
+
+/**
+ * @public
+ *
+ * Definition for the complete navigation tree, including body and footer
+ */
+export interface NavigationTreeDefinition {
+ /**
+ * Main content of the navigation. Can contain any number of "cloudLink", "recentlyAccessed"
+ * or "group" items. Be mindeful though, with great power comes great responsibility.
+ * */
+ body?: RootNavigationItemDefinition[];
+ /**
+ * Footer content of the navigation. Can contain any number of "cloudLink", "recentlyAccessed"
+ * or "group" items. Be mindeful though, with great power comes great responsibility.
+ * */
+ footer?: RootNavigationItemDefinition[];
+}
+
+/**
+ * @public
+ *
+ * A project navigation definition that can be passed to the `` component
+ * or when calling `setNavigation()` on the serverless plugin.
+ */
+export interface ProjectNavigationDefinition {
+ /**
+ * The URL href for the home link
+ */
+ homeRef: string;
+ /**
+ * A navigation tree structure with object items containing labels, links, and sub-items
+ * for a project. Use it if you only need to configure your project navigation and leave
+ * all the other navigation items to the default (Recently viewed items, Management, etc.)
+ */
+ projectNavigationTree?: ProjectNavigationTreeDefinition;
+ /**
+ * A navigation tree structure with object items containing labels, links, and sub-items
+ * that defines a complete side navigation. This configuration overrides `projectNavigationTree`
+ * if both are provided.
+ */
+ navigationTree?: NavigationTreeDefinition;
+}
+
+/**
+ * @private
+ *
+ * Function to unregister a navigation node from its parent.
+ */
+export type UnRegisterFunction = (id: string) => void;
+
+/**
+ * @internal
+ *
+ * A function to register a navigation node on its parent.
+ */
+export type RegisterFunction = (navNode: ChromeProjectNavigationNodeEnhanced) => {
+ /** The function to unregister the node. */
unregister: UnRegisterFunction;
+ /** The full path of the node in the navigation tree. */
path: string[];
};
-
-export interface NodeProps {
- children?: ((deepLink?: ChromeNavLink) => ReactNode) | ReactNode;
- id?: string;
- title?: string;
- link?: string;
- // Temp to test removing nav nodes
- onRemove?: () => void;
-}
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/use_init_navnode.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/use_init_navnode.ts
deleted file mode 100644
index 6a28f0e07b764..0000000000000
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/use_init_navnode.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { ChromeNavLink } from '@kbn/core-chrome-browser';
-import { useCallback, useEffect, useMemo, useRef } from 'react';
-import useObservable from 'react-use/lib/useObservable';
-
-import { useNavigation as useNavigationServices } from '../../services';
-import { InternalNavigationNode, NodeProps, RegisterFunction, UnRegisterFunction } from './types';
-import { useRegisterTreeNode } from './use_register_tree_node';
-
-function getIdFromNavigationNode({ id: _id, link, title }: NodeProps): string {
- const id = _id ?? link;
-
- if (!id) {
- throw new Error(`Id or link prop missing for navigation item [${title}]`);
- }
-
- return id;
-}
-
-function createInternalNavNode(
- id: string,
- _navNode: NodeProps,
- deepLinks: Readonly
-): InternalNavigationNode | null {
- const { children, onRemove, link, ...navNode } = _navNode;
- const deepLink = deepLinks.find((dl) => dl.id === link);
- const isLinkActive = isNodeActive({ link, deepLink });
-
- const titleFromDeepLinkOrChildren = typeof children === 'string' ? children : deepLink?.title;
- let title = navNode.title ?? titleFromDeepLinkOrChildren;
-
- if (!title || title.trim().length === 0) {
- if (isLinkActive) {
- throw new Error(`Title prop missing for navigation item [${id}]`);
- } else {
- // No title provided but the node is disabled, so we can safely set it to an empty string
- title = '';
- }
- }
-
- if (!isLinkActive) {
- return null;
- }
-
- return {
- ...navNode,
- id,
- title,
- deepLink,
- };
-}
-
-function isNodeActive({ link, deepLink }: { link?: string; deepLink?: ChromeNavLink }) {
- if (link && !deepLink) {
- // If a link is provided, but no deepLink is found, don't render anything
- return false;
- }
- return true;
-}
-
-export const useInitNavnode = (node: NodeProps) => {
- /**
- * Map of children nodes
- */
- const childrenNodes = useRef>({});
- /**
- * Flag to indicate if the current node has been registered
- */
- const isRegistered = useRef(false);
- /**
- * Reference to the unregister function
- */
- const unregisterRef = useRef();
- /**
- * Map to keep track of the order of the children when they mount.
- * This allows us to keep in sync the nav tree sent to the Chrome service
- * with the order of the DOM elements
- */
- const orderChildrenRef = useRef>({});
- /**
- * Index to keep track of the order of the children when they mount.
- */
- const idx = useRef(0);
-
- /**
- * The current node path, including all of its parents. We'll use it to match it against
- * the list of active routes based on current URL location (passed by the Chrome service)
- */
- const nodePath = useRef([]);
-
- const { navLinks$ } = useNavigationServices();
- const deepLinks = useObservable(navLinks$, []);
- const { register: registerNodeOnParent } = useRegisterTreeNode();
-
- const id = getIdFromNavigationNode(node);
-
- const internalNavNode = useMemo(
- () => createInternalNavNode(id, node, deepLinks),
- [node, id, deepLinks]
- );
-
- const register = useCallback(() => {
- if (internalNavNode) {
- const children = Object.values(childrenNodes.current).sort((a, b) => {
- const aOrder = orderChildrenRef.current[a.id];
- const bOrder = orderChildrenRef.current[b.id];
- return aOrder - bOrder;
- });
-
- const { unregister, path } = registerNodeOnParent({
- ...internalNavNode,
- children: children.length ? children : undefined,
- });
-
- nodePath.current = [...path, internalNavNode.id];
- unregisterRef.current = unregister;
- isRegistered.current = true;
- }
- }, [internalNavNode, registerNodeOnParent]);
-
- const registerChildNode = useCallback(
- (childNode) => {
- childrenNodes.current[childNode.id] = childNode;
- orderChildrenRef.current[childNode.id] = idx.current++;
-
- if (isRegistered.current) {
- register();
- }
-
- const unregisterFn = () => {
- // Remove the child from this children map
- const updatedItems = { ...childrenNodes.current };
- delete updatedItems[childNode.id];
- childrenNodes.current = updatedItems;
-
- if (isRegistered.current) {
- // Update the parent tree
- register();
- }
- };
-
- return {
- unregister: unregisterFn,
- path: [...nodePath.current],
- };
- },
- [register]
- );
-
- const unregister = useCallback(() => {
- isRegistered.current = false;
- if (unregisterRef.current) {
- unregisterRef.current();
- }
- }, []);
-
- useEffect(() => {
- if (internalNavNode) {
- register();
- } else {
- unregister();
- }
- }, [internalNavNode, unregister, register]);
-
- useEffect(() => unregister, [unregister]);
-
- return useMemo(
- () => ({
- navNode: internalNavNode,
- registerChildNode,
- }),
- [internalNavNode, registerChildNode]
- );
-};
From 854622d178ab9f229ff95088e3dc6351d91d27de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Tue, 30 May 2023 12:03:56 +0100
Subject: [PATCH 10/18] Allow href to be passed in node definition
---
.../chrome/core-chrome-browser/src/project_navigation.ts | 6 ++++++
.../src/ui/v2/components/navigation_section_ui.tsx | 2 +-
packages/shared-ux/chrome/navigation/src/ui/v2/types.ts | 6 ++++++
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts
index cd8f634c07836..43acc3897307e 100644
--- a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts
+++ b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts
@@ -39,6 +39,12 @@ export interface ChromeProjectNavigationNode {
icon?: string;
/** Optional children of the navigation node */
children?: ChromeProjectNavigationNode[];
+ /**
+ * Temporarilly we allow href to be passed.
+ * Once all the deeplinks will be exposed in packages we will not allow href anymore
+ * and force deeplink id to be passed
+ */
+ href?: string;
}
/** @public */
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_section_ui.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_section_ui.tsx
index 1efa885ace4aa..737cdf0376411 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_section_ui.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_section_ui.tsx
@@ -23,7 +23,7 @@ const navigationNodeToEuiItem = (
item: ChromeProjectNavigationNodeEnhanced,
{ navigateToUrl, basePath }: { navigateToUrl: NavigateToUrlFn; basePath: BasePathService }
): EuiSideNavItemType => {
- const href = item.deepLink?.href;
+ const href = item.deepLink?.href ?? item.href;
const id = item.path ? item.path.join('.') : item.id;
return {
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/types.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/types.ts
index 27dcd22654bde..e04f8af8bdf67 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/types.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/types.ts
@@ -31,6 +31,12 @@ export interface NodeDefinition
icon?: string;
/** Optional children of the navigation node */
children?: Array>;
+ /**
+ * Temporarilly we allow href to be passed.
+ * Once all the deeplinks will be exposed in packages we will not allow href anymore
+ * and force deeplink id to be passed
+ */
+ href?: string;
}
/**
From 31c712232ac8f5764117bd726275013082eee7d1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Tue, 30 May 2023 12:04:52 +0100
Subject: [PATCH 11/18] Update group preset with href links
---
.../src/ui/v2/nav_tree_presets/analytics.ts | 13 ++-
.../src/ui/v2/nav_tree_presets/devtools.ts | 12 +-
.../src/ui/v2/nav_tree_presets/management.ts | 106 +++++++++++-------
.../src/ui/v2/nav_tree_presets/ml.ts | 53 +++++----
4 files changed, 118 insertions(+), 66 deletions(-)
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/analytics.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/analytics.ts
index 119b47cd5db3a..6435fe6b0f55e 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/analytics.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/analytics.ts
@@ -8,7 +8,7 @@
import type { NodeDefinitionWithChildren } from '.';
-export type ID = 'sharedux:analytics' | 'root' | 'discover' | 'dashboard' | 'visualize';
+export type ID = 'sharedux:analytics' | 'root' | 'discover' | 'dashboard' | 'visualize_library';
export const analytics: NodeDefinitionWithChildren = {
// Make sure we have a unique id otherwise it might override a custom id from the project
@@ -20,16 +20,19 @@ export const analytics: NodeDefinitionWithChildren = {
id: 'root',
children: [
{
- id: 'discover',
title: 'Discover',
+ id: 'discover',
+ href: '/app/discover',
},
{
- id: 'dashboard',
title: 'Dashboard',
+ id: 'dashboard',
+ href: '/app/dashboards',
},
{
- id: 'visualize',
- title: 'Visualize library',
+ id: 'visualize_library',
+ title: 'Visualize Library',
+ href: '/app/visualize',
},
],
},
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/devtools.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/devtools.ts
index e0c0dcffcad5f..03b2eff015433 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/devtools.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/devtools.ts
@@ -25,20 +25,24 @@ export const devtools: NodeDefinitionWithChildren = {
id: 'root',
children: [
{
- title: 'Console',
id: 'console',
+ title: 'Console',
+ href: '/app/dev_tools#/console',
},
{
- title: 'Search profiler',
id: 'search_profiler',
+ title: 'Search profiler',
+ href: '/app/dev_tools#/searchprofiler',
},
{
- title: 'Grok debugger',
id: 'grok_debugger',
+ title: 'Grok debugger',
+ href: '/app/dev_tools#/grokdebugger',
},
{
- title: 'Painless lab',
id: 'painless_lab',
+ title: 'Painless lab',
+ href: '/app/dev_tools#/painless_lab',
},
],
},
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/management.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/management.ts
index 3483d0b03ed11..db87f84ec37d2 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/management.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/management.ts
@@ -55,170 +55,200 @@ export const management: NodeDefinitionWithChildren = {
icon: 'gear',
children: [
{
- title: '',
id: 'root',
+ title: '',
children: [
{
- title: 'Stack monitoring',
id: 'stack_monitoring',
+ title: 'Stack monitoring',
+ href: '/app/monitoring',
},
],
},
{
- title: 'Integration management',
id: 'integration_management',
+ title: 'Integration management',
children: [
{
- title: 'Integrations',
id: 'integrations',
+ title: 'Integrations',
+ href: '/app/integrations',
},
{
- title: 'Fleet',
id: 'fleet',
+ title: 'Fleet',
+ href: '/app/fleet',
},
{
- title: 'Osquery',
id: 'osquery',
+ title: 'Osquery',
+ href: '/app/osquery',
},
],
},
{
- title: 'Stack management',
id: 'stack_management',
+ title: 'Stack management',
children: [
{
- title: 'Ingest',
id: 'ingest',
+ title: 'Ingest',
children: [
{
- title: 'Ingest pipelines',
id: 'ingest_pipelines',
+ title: 'Ingest pipelines',
+ href: '/app/management/ingest/ingest_pipelines',
},
{
- title: 'Logstash pipelines',
id: 'logstash_pipelines',
+ title: 'Logstash pipelines',
+ href: '/app/management/ingest/pipelines',
},
],
},
{
- title: 'Data',
id: 'data',
+ title: 'Data',
children: [
{
- title: 'Index management',
id: 'index_management',
+ title: 'Index management',
+ href: '/app/management/data/index_management',
},
{
- title: 'Index lifecycle policies',
id: 'index_lifecycle_policies',
+ title: 'Index lifecycle policies',
+ href: '/app/management/data/index_lifecycle_management',
},
{
- title: 'Snapshot and restore',
id: 'snapshot_and_restore',
+ title: 'Snapshot and restore',
+ href: 'app/management/data/snapshot_restore',
},
{
- title: 'Rollup jobs',
id: 'rollup_jobs',
+ title: 'Rollup jobs',
+ href: '/app/management/data/rollup_jobs',
},
{
- title: 'Transforms',
id: 'transforms',
+ title: 'Transforms',
+ href: '/app/management/data/transform',
},
{
- title: 'Cross-cluster replication',
id: 'cross_cluster_replication',
+ title: 'Cross-cluster replication',
+ href: '/app/management/data/cross_cluster_replication',
},
{
- title: 'Remote clusters',
id: 'remote_clusters',
+ title: 'Remote clusters',
+ href: '/app/management/data/remote_clusters',
},
],
},
{
- title: 'Alerts and insights',
id: 'alerts_and_insights',
+ title: 'Alerts and insights',
children: [
{
- title: 'Rules',
id: 'rules',
+ title: 'Rules',
+ href: '/app/management/insightsAndAlerting/triggersActions/rules',
},
{
- title: 'Cases',
id: 'cases',
+ title: 'Cases',
+ href: '/app/management/insightsAndAlerting/cases',
},
{
- title: 'Connectors',
id: 'connectors',
+ title: 'Connectors',
+ href: '/app/management/insightsAndAlerting/triggersActionsConnectors/connectors',
},
{
- title: 'Reporting',
id: 'reporting',
+ title: 'Reporting',
+ href: '/app/management/insightsAndAlerting/reporting',
},
{
- title: 'Machine learning',
id: 'machine_learning',
+ title: 'Machine learning',
+ href: '/app/management/insightsAndAlerting/jobsListLink',
},
{
- title: 'Watcher',
id: 'watcher',
+ title: 'Watcher',
+ href: '/app/management/insightsAndAlerting/watcher',
},
],
},
{
- title: 'Security',
id: 'security',
+ title: 'Security',
children: [
{
- title: 'Users',
id: 'users',
+ title: 'Users',
+ href: '/app/management/security/users',
},
{
- title: 'Roles',
id: 'roles',
+ title: 'Roles',
+ href: '/app/management/security/roles',
},
{
- title: 'Role mappings',
id: 'role_mappings',
+ title: 'Role mappings',
+ href: '/app/management/security/role_mappings',
},
{
- title: 'API keys',
id: 'api_keys',
+ title: 'API keys',
+ href: '/app/management/security/api_keys',
},
],
},
{
- title: 'Kibana',
id: 'kibana',
+ title: 'Kibana',
children: [
{
- title: 'Data view',
id: 'data_views',
+ title: 'Data view',
+ href: '/app/management/kibana/dataViews',
},
{
- title: 'Saved objects',
id: 'saved_objects',
+ title: 'Saved objects',
+ href: '/app/management/kibana/objects',
},
{
- title: 'Tags',
id: 'tags',
+ title: 'Tags',
+ href: '/app/management/kibana/tags',
},
{
- title: 'Search sessions',
id: 'search_sessions',
+ title: 'Search sessions',
+ href: '/app/management/kibana/search_sessions',
},
{
- title: 'Spaces',
id: 'spaces',
+ title: 'Spaces',
+ href: '/app/management/kibana/spaces',
},
{
- title: 'Advanced settings',
id: 'advanced_settings',
+ title: 'Advanced settings',
+ href: '/app/management/kibana/settings',
},
],
},
{
- title: 'Upgrade assistant',
id: 'upgrade_assistant',
+ title: 'Upgrade assistant',
+ href: '/app/management/stack/upgrade_assistant',
},
],
},
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/ml.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/ml.ts
index d11a97827490c..ba3b7e0645d67 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/ml.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/ml.ts
@@ -41,12 +41,14 @@ export const ml: NodeDefinitionWithChildren = {
id: 'root',
children: [
{
- title: 'Overview',
id: 'overview',
+ title: 'Overview',
+ href: '/app/ml/overview',
},
{
- title: 'Notifications',
id: 'notifications',
+ title: 'Notifications',
+ href: '/app/ml/notifications',
},
],
},
@@ -55,80 +57,93 @@ export const ml: NodeDefinitionWithChildren = {
id: 'anomaly_detection',
children: [
{
- title: 'Jobs',
id: 'jobs',
+ title: 'Jobs',
+ href: '/app/ml/jobs',
},
{
- title: 'Anomaly explorer',
id: 'explorer',
+ title: 'Anomaly explorer',
+ href: '/app/ml/explorer',
},
{
- title: 'Single metric viewer',
id: 'single_metric_viewer',
+ title: 'Single metric viewer',
+ href: '/app/ml/timeseriesexplorer',
},
{
- title: 'Settings',
id: 'settings',
+ title: 'Settings',
+ href: '/app/ml/settings',
},
],
},
{
- title: 'Data frame analytics',
id: 'data_frame_analytics',
+ title: 'Data frame analytics',
children: [
{
- title: 'Jobs',
id: 'jobs',
+ title: 'Jobs',
+ href: '/app/ml/data_frame_analytics',
},
{
- title: 'Results explorer',
id: 'results_explorer',
+ title: 'Results explorer',
+ href: '/app/ml/data_frame_analytics/exploration',
},
{
- title: 'Analytics map',
id: 'analytics_map',
+ title: 'Analytics map',
+ href: '/app/ml/data_frame_analytics/map',
},
],
},
{
- title: 'Model management',
id: 'model_management',
+ title: 'Model management',
children: [
{
- title: 'Trained models',
id: 'trained_models',
+ title: 'Trained models',
+ href: '/app/ml/trained_models',
},
{
- title: 'Nodes',
id: 'nodes',
+ title: 'Nodes',
+ href: '/app/ml/nodes',
},
],
},
{
- title: 'Data visualizer',
id: 'data_visualizer',
+ title: 'Data visualizer',
children: [
{
- title: 'File',
id: 'file',
+ title: 'File',
+ href: '/app/ml/filedatavisualizer',
},
{
- title: 'Data view',
id: 'data_view',
+ title: 'Data view',
+ href: '/app/ml/datavisualizer_index_select',
},
],
},
{
- title: 'AIOps labs',
id: 'aiops_labs',
+ title: 'AIOps labs',
children: [
{
- title: 'Explain log rate spikes',
id: 'explain_log_rate_spikes',
+ title: 'Explain log rate spikes',
+ href: '/app/ml/aiops/explain_log_rate_spikes_index_select',
},
{
- title: 'Log pattern analysis',
id: 'log_pattern_analysis',
+ title: 'Log pattern analysis',
+ href: '/app/ml/aiops/log_categorization_index_select',
},
],
},
From 6db8e5f4d775f2f578f44ca21e94c19d6fc7856b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Tue, 30 May 2023 12:44:57 +0100
Subject: [PATCH 12/18] Update jest tests
---
.../src/ui/v2/components/navigation.test.tsx | 25 +-
.../ui/v2/components/navigation_bucket.tsx | 7 +-
.../ui/v2/default_navigation.test.helpers.ts | 913 +++++++++---------
.../src/ui/v2/default_navigation.test.tsx | 92 +-
4 files changed, 557 insertions(+), 480 deletions(-)
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.test.tsx
index 80bc5cd0c7abe..7206da81e261a 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.test.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.test.tsx
@@ -14,7 +14,12 @@ import type { ChromeNavLink } from '@kbn/core-chrome-browser';
import { getServicesMock } from '../../../../mocks/src/jest';
import { NavigationProvider } from '../../../services';
import { Navigation } from './navigation';
-import { defaultNavigationTree } from '../default_navigation.test.helpers';
+import {
+ defaultAnalyticsNavGroup,
+ defaultDevtoolsNavGroup,
+ defaultManagementNavGroup,
+ defaultMlNavGroup,
+} from '../default_navigation.test.helpers';
describe('', () => {
const services = getServicesMock();
@@ -372,12 +377,24 @@ describe('', () => {
expect(onProjectNavigationChange).toHaveBeenCalled();
const lastCall =
onProjectNavigationChange.mock.calls[onProjectNavigationChange.mock.calls.length - 1];
- const [navTree] = lastCall;
+ const [navTreeGenerated] = lastCall;
- expect(navTree).toEqual({
+ expect(navTreeGenerated).toEqual({
homeRef: 'https://elastic.co',
- navigationTree: defaultNavigationTree.map(({ type, ...rest }) => rest),
+ navigationTree: expect.any(Array),
});
+
+ // The default navigation tree for analytics
+ expect(navTreeGenerated.navigationTree[0]).toEqual(defaultAnalyticsNavGroup);
+
+ // The default navigation tree for ml
+ expect(navTreeGenerated.navigationTree[1]).toEqual(defaultMlNavGroup);
+
+ // The default navigation tree for devtools+
+ expect(navTreeGenerated.navigationTree[2]).toEqual(defaultDevtoolsNavGroup);
+
+ // The default navigation tree for management
+ expect(navTreeGenerated.navigationTree[3]).toEqual(defaultManagementNavGroup);
});
test('should render cloud link', async () => {
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_bucket.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_bucket.tsx
index 5d8cc8a989e0c..ac5f9691f6f03 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_bucket.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_bucket.tsx
@@ -51,10 +51,7 @@ export const NavigationBucket: FC = ({
{item.children ? (
= ({
{renderItems(item.children)}
) : (
-
+
)}
);
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.helpers.ts b/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.helpers.ts
index 2b5f1a85e5d56..35d0cd1728a80 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.helpers.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.helpers.ts
@@ -12,438 +12,485 @@
* NOTE: This will have to be updated once we add the deep link ids as each of the node
* will contain the deep link information.
*/
+
+export const defaultAnalyticsNavGroup = {
+ id: 'sharedux:analytics',
+ title: 'Data exploration',
+ icon: 'stats',
+ path: ['sharedux:analytics'],
+ children: [
+ {
+ id: 'root',
+ path: ['sharedux:analytics', 'root'],
+ title: '',
+ children: [
+ {
+ title: 'Discover',
+ id: 'discover',
+ href: '/app/discover',
+ path: ['sharedux:analytics', 'root', 'discover'],
+ },
+ {
+ title: 'Dashboard',
+ id: 'dashboard',
+ href: '/app/dashboards',
+ path: ['sharedux:analytics', 'root', 'dashboard'],
+ },
+ {
+ id: 'visualize_library',
+ title: 'Visualize Library',
+ href: '/app/visualize',
+ path: ['sharedux:analytics', 'root', 'visualize_library'],
+ },
+ ],
+ },
+ ],
+};
+
+export const defaultMlNavGroup = {
+ id: 'sharedux:ml',
+ title: 'Machine learning',
+ icon: 'indexMapping',
+ path: ['sharedux:ml'],
+ children: [
+ {
+ title: '',
+ id: 'root',
+ path: ['sharedux:ml', 'root'],
+ children: [
+ {
+ id: 'overview',
+ title: 'Overview',
+ href: '/app/ml/overview',
+ path: ['sharedux:ml', 'root', 'overview'],
+ },
+ {
+ id: 'notifications',
+ title: 'Notifications',
+ href: '/app/ml/notifications',
+ path: ['sharedux:ml', 'root', 'notifications'],
+ },
+ ],
+ },
+ {
+ title: 'Anomaly detection',
+ id: 'anomaly_detection',
+ path: ['sharedux:ml', 'anomaly_detection'],
+ children: [
+ {
+ id: 'jobs',
+ title: 'Jobs',
+ href: '/app/ml/jobs',
+ path: ['sharedux:ml', 'anomaly_detection', 'jobs'],
+ },
+ {
+ id: 'explorer',
+ title: 'Anomaly explorer',
+ href: '/app/ml/explorer',
+ path: ['sharedux:ml', 'anomaly_detection', 'explorer'],
+ },
+ {
+ id: 'single_metric_viewer',
+ title: 'Single metric viewer',
+ href: '/app/ml/timeseriesexplorer',
+ path: ['sharedux:ml', 'anomaly_detection', 'single_metric_viewer'],
+ },
+ {
+ id: 'settings',
+ title: 'Settings',
+ href: '/app/ml/settings',
+ path: ['sharedux:ml', 'anomaly_detection', 'settings'],
+ },
+ ],
+ },
+ {
+ id: 'data_frame_analytics',
+ title: 'Data frame analytics',
+ path: ['sharedux:ml', 'data_frame_analytics'],
+ children: [
+ {
+ id: 'jobs',
+ title: 'Jobs',
+ href: '/app/ml/data_frame_analytics',
+ path: ['sharedux:ml', 'data_frame_analytics', 'jobs'],
+ },
+ {
+ id: 'results_explorer',
+ title: 'Results explorer',
+ href: '/app/ml/data_frame_analytics/exploration',
+ path: ['sharedux:ml', 'data_frame_analytics', 'results_explorer'],
+ },
+ {
+ id: 'analytics_map',
+ title: 'Analytics map',
+ href: '/app/ml/data_frame_analytics/map',
+ path: ['sharedux:ml', 'data_frame_analytics', 'analytics_map'],
+ },
+ ],
+ },
+ {
+ id: 'model_management',
+ title: 'Model management',
+ path: ['sharedux:ml', 'model_management'],
+ children: [
+ {
+ id: 'trained_models',
+ title: 'Trained models',
+ href: '/app/ml/trained_models',
+ path: ['sharedux:ml', 'model_management', 'trained_models'],
+ },
+ {
+ id: 'nodes',
+ title: 'Nodes',
+ href: '/app/ml/nodes',
+ path: ['sharedux:ml', 'model_management', 'nodes'],
+ },
+ ],
+ },
+ {
+ id: 'data_visualizer',
+ title: 'Data visualizer',
+ path: ['sharedux:ml', 'data_visualizer'],
+ children: [
+ {
+ id: 'file',
+ title: 'File',
+ href: '/app/ml/filedatavisualizer',
+ path: ['sharedux:ml', 'data_visualizer', 'file'],
+ },
+ {
+ id: 'data_view',
+ title: 'Data view',
+ href: '/app/ml/datavisualizer_index_select',
+ path: ['sharedux:ml', 'data_visualizer', 'data_view'],
+ },
+ ],
+ },
+ {
+ id: 'aiops_labs',
+ title: 'AIOps labs',
+ path: ['sharedux:ml', 'aiops_labs'],
+ children: [
+ {
+ id: 'explain_log_rate_spikes',
+ title: 'Explain log rate spikes',
+ href: '/app/ml/aiops/explain_log_rate_spikes_index_select',
+ path: ['sharedux:ml', 'aiops_labs', 'explain_log_rate_spikes'],
+ },
+ {
+ id: 'log_pattern_analysis',
+ title: 'Log pattern analysis',
+ href: '/app/ml/aiops/log_categorization_index_select',
+ path: ['sharedux:ml', 'aiops_labs', 'log_pattern_analysis'],
+ },
+ ],
+ },
+ ],
+};
+
+export const defaultDevtoolsNavGroup = {
+ title: 'Developer tools',
+ id: 'sharedux:devtools',
+ icon: 'editorCodeBlock',
+ path: ['sharedux:devtools'],
+ children: [
+ {
+ id: 'root',
+ path: ['sharedux:devtools', 'root'],
+ title: '',
+ children: [
+ {
+ id: 'console',
+ title: 'Console',
+ href: '/app/dev_tools#/console',
+ path: ['sharedux:devtools', 'root', 'console'],
+ },
+ {
+ id: 'search_profiler',
+ title: 'Search profiler',
+ href: '/app/dev_tools#/searchprofiler',
+ path: ['sharedux:devtools', 'root', 'search_profiler'],
+ },
+ {
+ id: 'grok_debugger',
+ title: 'Grok debugger',
+ href: '/app/dev_tools#/grokdebugger',
+ path: ['sharedux:devtools', 'root', 'grok_debugger'],
+ },
+ {
+ id: 'painless_lab',
+ title: 'Painless lab',
+ href: '/app/dev_tools#/painless_lab',
+ path: ['sharedux:devtools', 'root', 'painless_lab'],
+ },
+ ],
+ },
+ ],
+};
+
+export const defaultManagementNavGroup = {
+ id: 'sharedux:management',
+ title: 'Management',
+ icon: 'gear',
+ path: ['sharedux:management'],
+ children: [
+ {
+ id: 'root',
+ title: '',
+ path: ['sharedux:management', 'root'],
+ children: [
+ {
+ id: 'stack_monitoring',
+ title: 'Stack monitoring',
+ href: '/app/monitoring',
+ path: ['sharedux:management', 'root', 'stack_monitoring'],
+ },
+ ],
+ },
+ {
+ id: 'integration_management',
+ title: 'Integration management',
+ path: ['sharedux:management', 'integration_management'],
+ children: [
+ {
+ id: 'integrations',
+ title: 'Integrations',
+ href: '/app/integrations',
+ path: ['sharedux:management', 'integration_management', 'integrations'],
+ },
+ {
+ id: 'fleet',
+ title: 'Fleet',
+ href: '/app/fleet',
+ path: ['sharedux:management', 'integration_management', 'fleet'],
+ },
+ {
+ id: 'osquery',
+ title: 'Osquery',
+ href: '/app/osquery',
+ path: ['sharedux:management', 'integration_management', 'osquery'],
+ },
+ ],
+ },
+ {
+ id: 'stack_management',
+ title: 'Stack management',
+ path: ['sharedux:management', 'stack_management'],
+ children: [
+ {
+ id: 'upgrade_assistant',
+ title: 'Upgrade assistant',
+ href: '/app/management/stack/upgrade_assistant',
+ path: ['sharedux:management', 'stack_management', 'upgrade_assistant'],
+ },
+ {
+ id: 'ingest',
+ title: 'Ingest',
+ path: ['sharedux:management', 'stack_management', 'ingest'],
+ children: [
+ {
+ id: 'ingest_pipelines',
+ title: 'Ingest pipelines',
+ href: '/app/management/ingest/ingest_pipelines',
+ path: ['sharedux:management', 'stack_management', 'ingest', 'ingest_pipelines'],
+ },
+ {
+ id: 'logstash_pipelines',
+ title: 'Logstash pipelines',
+ href: '/app/management/ingest/pipelines',
+ path: ['sharedux:management', 'stack_management', 'ingest', 'logstash_pipelines'],
+ },
+ ],
+ },
+ {
+ id: 'data',
+ title: 'Data',
+ path: ['sharedux:management', 'stack_management', 'data'],
+ children: [
+ {
+ id: 'index_management',
+ title: 'Index management',
+ href: '/app/management/data/index_management',
+ path: ['sharedux:management', 'stack_management', 'data', 'index_management'],
+ },
+ {
+ id: 'index_lifecycle_policies',
+ title: 'Index lifecycle policies',
+ href: '/app/management/data/index_lifecycle_management',
+ path: ['sharedux:management', 'stack_management', 'data', 'index_lifecycle_policies'],
+ },
+ {
+ id: 'snapshot_and_restore',
+ title: 'Snapshot and restore',
+ href: 'app/management/data/snapshot_restore',
+ path: ['sharedux:management', 'stack_management', 'data', 'snapshot_and_restore'],
+ },
+ {
+ id: 'rollup_jobs',
+ title: 'Rollup jobs',
+ href: '/app/management/data/rollup_jobs',
+ path: ['sharedux:management', 'stack_management', 'data', 'rollup_jobs'],
+ },
+ {
+ id: 'transforms',
+ title: 'Transforms',
+ href: '/app/management/data/transform',
+ path: ['sharedux:management', 'stack_management', 'data', 'transforms'],
+ },
+ {
+ id: 'cross_cluster_replication',
+ title: 'Cross-cluster replication',
+ href: '/app/management/data/cross_cluster_replication',
+ path: [
+ 'sharedux:management',
+ 'stack_management',
+ 'data',
+ 'cross_cluster_replication',
+ ],
+ },
+ {
+ id: 'remote_clusters',
+ title: 'Remote clusters',
+ href: '/app/management/data/remote_clusters',
+ path: ['sharedux:management', 'stack_management', 'data', 'remote_clusters'],
+ },
+ ],
+ },
+ {
+ id: 'alerts_and_insights',
+ title: 'Alerts and insights',
+ path: ['sharedux:management', 'stack_management', 'alerts_and_insights'],
+ children: [
+ {
+ id: 'rules',
+ title: 'Rules',
+ href: '/app/management/insightsAndAlerting/triggersActions/rules',
+ path: ['sharedux:management', 'stack_management', 'alerts_and_insights', 'rules'],
+ },
+ {
+ id: 'cases',
+ title: 'Cases',
+ href: '/app/management/insightsAndAlerting/cases',
+ path: ['sharedux:management', 'stack_management', 'alerts_and_insights', 'cases'],
+ },
+ {
+ id: 'connectors',
+ title: 'Connectors',
+ href: '/app/management/insightsAndAlerting/triggersActionsConnectors/connectors',
+ path: [
+ 'sharedux:management',
+ 'stack_management',
+ 'alerts_and_insights',
+ 'connectors',
+ ],
+ },
+ {
+ id: 'reporting',
+ title: 'Reporting',
+ href: '/app/management/insightsAndAlerting/reporting',
+ path: ['sharedux:management', 'stack_management', 'alerts_and_insights', 'reporting'],
+ },
+ {
+ id: 'machine_learning',
+ title: 'Machine learning',
+ href: '/app/management/insightsAndAlerting/jobsListLink',
+ path: [
+ 'sharedux:management',
+ 'stack_management',
+ 'alerts_and_insights',
+ 'machine_learning',
+ ],
+ },
+ {
+ id: 'watcher',
+ title: 'Watcher',
+ href: '/app/management/insightsAndAlerting/watcher',
+ path: ['sharedux:management', 'stack_management', 'alerts_and_insights', 'watcher'],
+ },
+ ],
+ },
+ {
+ id: 'security',
+ title: 'Security',
+ path: ['sharedux:management', 'stack_management', 'security'],
+ children: [
+ {
+ id: 'users',
+ title: 'Users',
+ href: '/app/management/security/users',
+ path: ['sharedux:management', 'stack_management', 'security', 'users'],
+ },
+ {
+ id: 'roles',
+ title: 'Roles',
+ href: '/app/management/security/roles',
+ path: ['sharedux:management', 'stack_management', 'security', 'roles'],
+ },
+ {
+ id: 'role_mappings',
+ title: 'Role mappings',
+ href: '/app/management/security/role_mappings',
+ path: ['sharedux:management', 'stack_management', 'security', 'role_mappings'],
+ },
+ {
+ id: 'api_keys',
+ title: 'API keys',
+ href: '/app/management/security/api_keys',
+ path: ['sharedux:management', 'stack_management', 'security', 'api_keys'],
+ },
+ ],
+ },
+ {
+ id: 'kibana',
+ title: 'Kibana',
+ path: ['sharedux:management', 'stack_management', 'kibana'],
+ children: [
+ {
+ id: 'data_views',
+ title: 'Data view',
+ href: '/app/management/kibana/dataViews',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'data_views'],
+ },
+ {
+ id: 'saved_objects',
+ title: 'Saved objects',
+ href: '/app/management/kibana/objects',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'saved_objects'],
+ },
+ {
+ id: 'tags',
+ title: 'Tags',
+ href: '/app/management/kibana/tags',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'tags'],
+ },
+ {
+ id: 'search_sessions',
+ title: 'Search sessions',
+ href: '/app/management/kibana/search_sessions',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'search_sessions'],
+ },
+ {
+ id: 'spaces',
+ title: 'Spaces',
+ href: '/app/management/kibana/spaces',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'spaces'],
+ },
+ {
+ id: 'advanced_settings',
+ title: 'Advanced settings',
+ href: '/app/management/kibana/settings',
+ path: ['sharedux:management', 'stack_management', 'kibana', 'advanced_settings'],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+};
+
export const defaultNavigationTree = [
- {
- type: 'navGroup',
- id: 'sharedux:analytics',
- title: 'Data exploration',
- icon: 'stats',
- path: ['sharedux:analytics'],
- children: [
- {
- id: 'root',
- path: ['sharedux:analytics', 'root'],
- title: '',
- children: [
- {
- id: 'discover',
- title: 'Discover',
- path: ['sharedux:analytics', 'root', 'discover'],
- },
- {
- id: 'dashboard',
- title: 'Dashboard',
- path: ['sharedux:analytics', 'root', 'dashboard'],
- },
- {
- id: 'visualize',
- title: 'Visualize library',
- path: ['sharedux:analytics', 'root', 'visualize'],
- },
- ],
- },
- ],
- },
- {
- type: 'navGroup',
- id: 'sharedux:ml',
- title: 'Machine learning',
- icon: 'indexMapping',
- path: ['sharedux:ml'],
- children: [
- {
- title: '',
- id: 'root',
- path: ['sharedux:ml', 'root'],
- children: [
- {
- title: 'Overview',
- id: 'overview',
- path: ['sharedux:ml', 'root', 'overview'],
- },
- {
- title: 'Notifications',
- id: 'notifications',
- path: ['sharedux:ml', 'root', 'notifications'],
- },
- ],
- },
- {
- title: 'Anomaly detection',
- id: 'anomaly_detection',
- path: ['sharedux:ml', 'anomaly_detection'],
- children: [
- {
- title: 'Jobs',
- id: 'jobs',
- path: ['sharedux:ml', 'anomaly_detection', 'jobs'],
- },
- {
- title: 'Anomaly explorer',
- id: 'explorer',
- path: ['sharedux:ml', 'anomaly_detection', 'explorer'],
- },
- {
- title: 'Single metric viewer',
- id: 'single_metric_viewer',
- path: ['sharedux:ml', 'anomaly_detection', 'single_metric_viewer'],
- },
- {
- title: 'Settings',
- id: 'settings',
- path: ['sharedux:ml', 'anomaly_detection', 'settings'],
- },
- ],
- },
- {
- title: 'Data frame analytics',
- id: 'data_frame_analytics',
- path: ['sharedux:ml', 'data_frame_analytics'],
- children: [
- {
- title: 'Jobs',
- id: 'jobs',
- path: ['sharedux:ml', 'data_frame_analytics', 'jobs'],
- },
- {
- title: 'Results explorer',
- id: 'results_explorer',
- path: ['sharedux:ml', 'data_frame_analytics', 'results_explorer'],
- },
- {
- title: 'Analytics map',
- id: 'analytics_map',
- path: ['sharedux:ml', 'data_frame_analytics', 'analytics_map'],
- },
- ],
- },
- {
- title: 'Model management',
- id: 'model_management',
- path: ['sharedux:ml', 'model_management'],
- children: [
- {
- title: 'Trained models',
- id: 'trained_models',
- path: ['sharedux:ml', 'model_management', 'trained_models'],
- },
- {
- title: 'Nodes',
- id: 'nodes',
- path: ['sharedux:ml', 'model_management', 'nodes'],
- },
- ],
- },
- {
- title: 'Data visualizer',
- id: 'data_visualizer',
- path: ['sharedux:ml', 'data_visualizer'],
- children: [
- {
- title: 'File',
- id: 'file',
- path: ['sharedux:ml', 'data_visualizer', 'file'],
- },
- {
- title: 'Data view',
- id: 'data_view',
- path: ['sharedux:ml', 'data_visualizer', 'data_view'],
- },
- ],
- },
- {
- title: 'AIOps labs',
- id: 'aiops_labs',
- path: ['sharedux:ml', 'aiops_labs'],
- children: [
- {
- title: 'Explain log rate spikes',
- id: 'explain_log_rate_spikes',
- path: ['sharedux:ml', 'aiops_labs', 'explain_log_rate_spikes'],
- },
- {
- title: 'Log pattern analysis',
- id: 'log_pattern_analysis',
- path: ['sharedux:ml', 'aiops_labs', 'log_pattern_analysis'],
- },
- ],
- },
- ],
- },
- {
- type: 'navGroup',
- title: 'Developer tools',
- id: 'sharedux:devtools',
- icon: 'editorCodeBlock',
- path: ['sharedux:devtools'],
- children: [
- {
- id: 'root',
- path: ['sharedux:devtools', 'root'],
- title: '',
- children: [
- {
- title: 'Console',
- id: 'console',
- path: ['sharedux:devtools', 'root', 'console'],
- },
- {
- title: 'Search profiler',
- id: 'search_profiler',
- path: ['sharedux:devtools', 'root', 'search_profiler'],
- },
- {
- title: 'Grok debugger',
- id: 'grok_debugger',
- path: ['sharedux:devtools', 'root', 'grok_debugger'],
- },
- {
- title: 'Painless lab',
- id: 'painless_lab',
- path: ['sharedux:devtools', 'root', 'painless_lab'],
- },
- ],
- },
- ],
- },
- {
- type: 'navGroup',
- id: 'sharedux:management',
- title: 'Management',
- icon: 'gear',
- path: ['sharedux:management'],
- children: [
- {
- title: '',
- id: 'root',
- path: ['sharedux:management', 'root'],
- children: [
- {
- title: 'Stack monitoring',
- id: 'stack_monitoring',
- path: ['sharedux:management', 'root', 'stack_monitoring'],
- },
- ],
- },
- {
- title: 'Integration management',
- id: 'integration_management',
- path: ['sharedux:management', 'integration_management'],
- children: [
- {
- title: 'Integrations',
- id: 'integrations',
- path: ['sharedux:management', 'integration_management', 'integrations'],
- },
- {
- title: 'Fleet',
- id: 'fleet',
- path: ['sharedux:management', 'integration_management', 'fleet'],
- },
- {
- title: 'Osquery',
- id: 'osquery',
- path: ['sharedux:management', 'integration_management', 'osquery'],
- },
- ],
- },
- {
- title: 'Stack management',
- id: 'stack_management',
- path: ['sharedux:management', 'stack_management'],
- children: [
- {
- title: 'Upgrade assistant',
- id: 'upgrade_assistant',
- path: ['sharedux:management', 'stack_management', 'upgrade_assistant'],
- },
- {
- title: 'Ingest',
- id: 'ingest',
- path: ['sharedux:management', 'stack_management', 'ingest'],
- children: [
- {
- title: 'Ingest pipelines',
- id: 'ingest_pipelines',
- path: ['sharedux:management', 'stack_management', 'ingest', 'ingest_pipelines'],
- },
- {
- title: 'Logstash pipelines',
- id: 'logstash_pipelines',
- path: ['sharedux:management', 'stack_management', 'ingest', 'logstash_pipelines'],
- },
- ],
- },
- {
- title: 'Data',
- id: 'data',
- path: ['sharedux:management', 'stack_management', 'data'],
- children: [
- {
- title: 'Index management',
- id: 'index_management',
- path: ['sharedux:management', 'stack_management', 'data', 'index_management'],
- },
- {
- title: 'Index lifecycle policies',
- id: 'index_lifecycle_policies',
- path: [
- 'sharedux:management',
- 'stack_management',
- 'data',
- 'index_lifecycle_policies',
- ],
- },
- {
- title: 'Snapshot and restore',
- id: 'snapshot_and_restore',
- path: ['sharedux:management', 'stack_management', 'data', 'snapshot_and_restore'],
- },
- {
- title: 'Rollup jobs',
- id: 'rollup_jobs',
- path: ['sharedux:management', 'stack_management', 'data', 'rollup_jobs'],
- },
- {
- title: 'Transforms',
- id: 'transforms',
- path: ['sharedux:management', 'stack_management', 'data', 'transforms'],
- },
- {
- title: 'Cross-cluster replication',
- id: 'cross_cluster_replication',
- path: [
- 'sharedux:management',
- 'stack_management',
- 'data',
- 'cross_cluster_replication',
- ],
- },
- {
- title: 'Remote clusters',
- id: 'remote_clusters',
- path: ['sharedux:management', 'stack_management', 'data', 'remote_clusters'],
- },
- ],
- },
- {
- title: 'Alerts and insights',
- id: 'alerts_and_insights',
- path: ['sharedux:management', 'stack_management', 'alerts_and_insights'],
- children: [
- {
- title: 'Rules',
- id: 'rules',
- path: ['sharedux:management', 'stack_management', 'alerts_and_insights', 'rules'],
- },
- {
- title: 'Cases',
- id: 'cases',
- path: ['sharedux:management', 'stack_management', 'alerts_and_insights', 'cases'],
- },
- {
- title: 'Connectors',
- id: 'connectors',
- path: [
- 'sharedux:management',
- 'stack_management',
- 'alerts_and_insights',
- 'connectors',
- ],
- },
- {
- title: 'Reporting',
- id: 'reporting',
- path: [
- 'sharedux:management',
- 'stack_management',
- 'alerts_and_insights',
- 'reporting',
- ],
- },
- {
- title: 'Machine learning',
- id: 'machine_learning',
- path: [
- 'sharedux:management',
- 'stack_management',
- 'alerts_and_insights',
- 'machine_learning',
- ],
- },
- {
- title: 'Watcher',
- id: 'watcher',
- path: ['sharedux:management', 'stack_management', 'alerts_and_insights', 'watcher'],
- },
- ],
- },
- {
- title: 'Security',
- id: 'security',
- path: ['sharedux:management', 'stack_management', 'security'],
- children: [
- {
- title: 'Users',
- id: 'users',
- path: ['sharedux:management', 'stack_management', 'security', 'users'],
- },
- {
- title: 'Roles',
- id: 'roles',
- path: ['sharedux:management', 'stack_management', 'security', 'roles'],
- },
- {
- title: 'Role mappings',
- id: 'role_mappings',
- path: ['sharedux:management', 'stack_management', 'security', 'role_mappings'],
- },
- {
- title: 'API keys',
- id: 'api_keys',
- path: ['sharedux:management', 'stack_management', 'security', 'api_keys'],
- },
- ],
- },
- {
- title: 'Kibana',
- id: 'kibana',
- path: ['sharedux:management', 'stack_management', 'kibana'],
- children: [
- {
- title: 'Data view',
- id: 'data_views',
- path: ['sharedux:management', 'stack_management', 'kibana', 'data_views'],
- },
- {
- title: 'Saved objects',
- id: 'saved_objects',
- path: ['sharedux:management', 'stack_management', 'kibana', 'saved_objects'],
- },
- {
- title: 'Tags',
- id: 'tags',
- path: ['sharedux:management', 'stack_management', 'kibana', 'tags'],
- },
- {
- title: 'Search sessions',
- id: 'search_sessions',
- path: ['sharedux:management', 'stack_management', 'kibana', 'search_sessions'],
- },
- {
- title: 'Spaces',
- id: 'spaces',
- path: ['sharedux:management', 'stack_management', 'kibana', 'spaces'],
- },
- {
- title: 'Advanced settings',
- id: 'advanced_settings',
- path: ['sharedux:management', 'stack_management', 'kibana', 'advanced_settings'],
- },
- ],
- },
- ],
- },
- ],
- },
+ defaultAnalyticsNavGroup,
+ defaultMlNavGroup,
+ defaultDevtoolsNavGroup,
+ defaultManagementNavGroup,
];
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.tsx
index 496f693a82ee4..0856fb4d2ced4 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.tsx
@@ -15,7 +15,12 @@ import { getServicesMock } from '../../../mocks/src/jest';
import { NavigationProvider } from '../../services';
import { DefaultNavigation } from './default_navigation';
import type { ProjectNavigationTreeDefinition, RootNavigationItemDefinition } from './types';
-import { defaultNavigationTree } from './default_navigation.test.helpers';
+import {
+ defaultAnalyticsNavGroup,
+ defaultDevtoolsNavGroup,
+ defaultManagementNavGroup,
+ defaultMlNavGroup,
+} from './default_navigation.test.helpers';
const defaultProps = {
homeRef: 'https://elastic.co',
@@ -371,47 +376,58 @@ describe('', () => {
expect(navTreeGenerated).toEqual({
homeRef: 'https://elastic.co',
- navigationTree: [
+ navigationTree: expect.any(Array),
+ });
+
+ // The project navigation tree passed
+ expect(navTreeGenerated.navigationTree[0]).toEqual({
+ id: 'group1',
+ title: 'Group 1',
+ path: ['group1'],
+ children: [
{
- id: 'group1',
- title: 'Group 1',
- path: ['group1'],
- children: [
- {
- id: 'item1',
- title: 'Item 1',
- path: ['group1', 'item1'],
- },
- {
- id: 'item2',
- path: ['group1', 'item2'],
- title: 'Title from deeplink!',
- deepLink: {
- id: 'item2',
- title: 'Title from deeplink!',
- baseUrl: '',
- url: '',
- href: '',
- },
- },
- {
- id: 'item3',
- title: 'Deeplink title overriden',
- path: ['group1', 'item3'],
- deepLink: {
- id: 'item2',
- title: 'Title from deeplink!',
- baseUrl: '',
- url: '',
- href: '',
- },
- },
- ],
+ id: 'item1',
+ title: 'Item 1',
+ path: ['group1', 'item1'],
+ },
+ {
+ id: 'item2',
+ path: ['group1', 'item2'],
+ title: 'Title from deeplink!',
+ deepLink: {
+ id: 'item2',
+ title: 'Title from deeplink!',
+ baseUrl: '',
+ url: '',
+ href: '',
+ },
+ },
+ {
+ id: 'item3',
+ title: 'Deeplink title overriden',
+ path: ['group1', 'item3'],
+ deepLink: {
+ id: 'item2',
+ title: 'Title from deeplink!',
+ baseUrl: '',
+ url: '',
+ href: '',
+ },
},
- // The default navigation tree is added at the end
- ...defaultNavigationTree.map(({ type, ...rest }) => rest),
],
});
+
+ // The default navigation tree for analytics
+ expect(navTreeGenerated.navigationTree[1]).toEqual(defaultAnalyticsNavGroup);
+
+ // The default navigation tree for ml
+ expect(navTreeGenerated.navigationTree[2]).toEqual(defaultMlNavGroup);
+
+ // The default navigation tree for devtools+
+ expect(navTreeGenerated.navigationTree[3]).toEqual(defaultDevtoolsNavGroup);
+
+ // The default navigation tree for management
+ expect(navTreeGenerated.navigationTree[4]).toEqual(defaultManagementNavGroup);
});
});
});
From 22ee67a94a52cc0a70a29ae92953b94dce717452 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Tue, 30 May 2023 16:17:06 +0100
Subject: [PATCH 13/18] Replace v1 with v2 components
---
packages/shared-ux/chrome/navigation/index.ts | 15 +-
.../src/ui/{v2 => }/components/cloud_link.tsx | 2 +-
.../src/ui/{v2 => }/components/index.ts | 0
.../{v2 => }/components/navigation.test.tsx | 4 +-
.../src/ui/{v2 => }/components/navigation.tsx | 12 +-
.../{v2 => }/components/navigation_bucket.tsx | 0
.../{v2 => }/components/navigation_footer.tsx | 0
.../{v2 => }/components/navigation_group.tsx | 0
.../{v2 => }/components/navigation_header.tsx | 6 +-
.../{v2 => }/components/navigation_item.tsx | 0
.../components/navigation_section_ui.tsx | 6 +-
.../ui/{v2 => }/components/navigation_ui.tsx | 10 +-
.../{v2 => }/components/recently_accessed.tsx | 8 +-
.../default_navigation.test.helpers.ts | 0
.../ui/{v2 => }/default_navigation.test.tsx | 0
.../src/ui/{v2 => }/default_navigation.tsx | 5 +-
.../navigation/src/ui/{v2 => }/hooks/index.ts | 0
.../src/ui/{v2 => }/hooks/use_init_navnode.ts | 0
.../{v2 => }/hooks/use_register_tree_node.ts | 0
.../chrome/navigation/src/ui/index.ts | 22 +
.../ui/{v2 => }/nav_tree_presets/analytics.ts | 0
.../ui/{v2 => }/nav_tree_presets/devtools.ts | 0
.../src/ui/{v2 => }/nav_tree_presets/index.ts | 0
.../{v2 => }/nav_tree_presets/management.ts | 0
.../src/ui/{v2 => }/nav_tree_presets/ml.ts | 0
.../navigation/src/ui/navigation.stories.tsx | 579 ++++++++++++++----
.../navigation/src/ui/navigation.test.tsx | 181 ------
.../chrome/navigation/src/ui/navigation.tsx | 200 ------
.../navigation/src/ui/navigation_bucket.tsx | 101 ---
.../navigation/src/ui/{v2 => }/types.ts | 7 +-
.../src/ui/v2/navigation.stories.tsx | 547 -----------------
31 files changed, 547 insertions(+), 1158 deletions(-)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/cloud_link.tsx (97%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/index.ts (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/navigation.test.tsx (99%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/navigation.tsx (93%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/navigation_bucket.tsx (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/navigation_footer.tsx (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/navigation_group.tsx (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/navigation_header.tsx (91%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/navigation_item.tsx (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/navigation_section_ui.tsx (93%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/navigation_ui.tsx (87%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/components/recently_accessed.tsx (88%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/default_navigation.test.helpers.ts (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/default_navigation.test.tsx (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/default_navigation.tsx (95%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/hooks/index.ts (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/hooks/use_init_navnode.ts (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/hooks/use_register_tree_node.ts (100%)
create mode 100644 packages/shared-ux/chrome/navigation/src/ui/index.ts
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/nav_tree_presets/analytics.ts (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/nav_tree_presets/devtools.ts (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/nav_tree_presets/index.ts (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/nav_tree_presets/management.ts (100%)
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/nav_tree_presets/ml.ts (100%)
delete mode 100644 packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx
delete mode 100644 packages/shared-ux/chrome/navigation/src/ui/navigation.tsx
delete mode 100644 packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx
rename packages/shared-ux/chrome/navigation/src/ui/{v2 => }/types.ts (99%)
delete mode 100644 packages/shared-ux/chrome/navigation/src/ui/v2/navigation.stories.tsx
diff --git a/packages/shared-ux/chrome/navigation/index.ts b/packages/shared-ux/chrome/navigation/index.ts
index 9f9e15bacb207..1d500b72117ec 100644
--- a/packages/shared-ux/chrome/navigation/index.ts
+++ b/packages/shared-ux/chrome/navigation/index.ts
@@ -7,7 +7,20 @@
*/
export { NavigationKibanaProvider, NavigationProvider } from './src/services';
-export { Navigation } from './src/ui/navigation';
+
+export { DefaultNavigation, Navigation } from './src/ui';
+
+export type {
+ NavigationTreeDefinition,
+ ProjectNavigationDefinition,
+ NodeDefinition,
+ NavigationGroupPreset,
+ GroupDefinition,
+ RecentlyAccessedDefinition,
+ CloudLinkDefinition,
+ RootNavigationItemDefinition,
+} from './src/ui';
+
export type {
ChromeNavigation,
ChromeNavigationViewModel,
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/cloud_link.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/cloud_link.tsx
similarity index 97%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/cloud_link.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/components/cloud_link.tsx
index 8e8ac169eded9..b71b949e47603 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/cloud_link.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/components/cloud_link.tsx
@@ -7,7 +7,7 @@
*/
import { EuiCollapsibleNavGroup, EuiLink } from '@elastic/eui';
import React, { FC } from 'react';
-import { getI18nStrings } from '../../i18n_strings';
+import { getI18nStrings } from '../i18n_strings';
const i18nTexts = getI18nStrings();
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/index.ts b/packages/shared-ux/chrome/navigation/src/ui/components/index.ts
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/index.ts
rename to packages/shared-ux/chrome/navigation/src/ui/components/index.ts
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.test.tsx
similarity index 99%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.test.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/components/navigation.test.tsx
index 7206da81e261a..3eef283038a14 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.test.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.test.tsx
@@ -11,8 +11,8 @@ import { render } from '@testing-library/react';
import { type Observable, of } from 'rxjs';
import type { ChromeNavLink } from '@kbn/core-chrome-browser';
-import { getServicesMock } from '../../../../mocks/src/jest';
-import { NavigationProvider } from '../../../services';
+import { getServicesMock } from '../../../mocks/src/jest';
+import { NavigationProvider } from '../../services';
import { Navigation } from './navigation';
import {
defaultAnalyticsNavGroup,
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.tsx
similarity index 93%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/components/navigation.tsx
index 2cbbe766059d6..6825c8a8084bf 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation.tsx
@@ -18,7 +18,7 @@ import React, {
} from 'react';
import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
-import { useNavigation as useNavigationServices } from '../../../services';
+import { useNavigation as useNavigationServices } from '../../services';
import { RegisterFunction, UnRegisterFunction } from '../types';
import { CloudLink } from './cloud_link';
import { NavigationFooter } from './navigation_footer';
@@ -53,9 +53,10 @@ interface Props {
* If set to true, the children will be rendered as is.
*/
unstyled?: boolean;
+ dataTestSubj?: string;
}
-export function Navigation({ children, homeRef, unstyled = false }: Props) {
+export function Navigation({ children, homeRef, unstyled = false, dataTestSubj }: Props) {
const { onProjectNavigationChange } = useNavigationServices();
// We keep a reference of the order of the children that register themselves when mounting.
@@ -119,7 +120,12 @@ export function Navigation({ children, homeRef, unstyled = false }: Props) {
return (
-
+
{children}
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_bucket.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_bucket.tsx
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_bucket.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/components/navigation_bucket.tsx
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_footer.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_footer.tsx
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_footer.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/components/navigation_footer.tsx
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_group.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_group.tsx
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_group.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/components/navigation_group.tsx
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_header.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_header.tsx
similarity index 91%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_header.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/components/navigation_header.tsx
index 92f93e8092979..e7db1d25aaddb 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_header.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_header.tsx
@@ -9,9 +9,9 @@
import React, { FC } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiHeaderLogo, EuiLoadingSpinner } from '@elastic/eui';
import useObservable from 'react-use/lib/useObservable';
-import { useNavigation as useServices } from '../../../services';
-import { ElasticMark } from '../../elastic_mark';
-import { getI18nStrings } from '../../i18n_strings';
+import { useNavigation as useServices } from '../../services';
+import { ElasticMark } from '../elastic_mark';
+import { getI18nStrings } from '../i18n_strings';
import '../../header_logo.scss';
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_item.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item.tsx
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_item.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/components/navigation_item.tsx
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_section_ui.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx
similarity index 93%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_section_ui.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx
index 737cdf0376411..a592e6cc539b2 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_section_ui.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx
@@ -14,9 +14,9 @@ import {
EuiSideNavItemType,
EuiText,
} from '@elastic/eui';
-import type { BasePathService, NavigateToUrlFn } from '../../../../types/internal';
-import { navigationStyles as styles } from '../../../styles';
-import { useNavigation as useServices } from '../../../services';
+import type { BasePathService, NavigateToUrlFn } from '../../../types/internal';
+import { navigationStyles as styles } from '../../styles';
+import { useNavigation as useServices } from '../../services';
import { ChromeProjectNavigationNodeEnhanced } from '../types';
const navigationNodeToEuiItem = (
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_ui.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_ui.tsx
similarity index 87%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_ui.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/components/navigation_ui.tsx
index 7c754b3a9ce76..a0f48aef8a112 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/navigation_ui.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_ui.tsx
@@ -14,9 +14,16 @@ interface Props {
homeRef: string;
unstyled?: boolean;
footerChildren?: React.ReactNode;
+ dataTestSubj?: string;
}
-export const NavigationUI: FC = ({ children, unstyled, footerChildren, homeRef }) => {
+export const NavigationUI: FC = ({
+ children,
+ unstyled,
+ footerChildren,
+ homeRef,
+ dataTestSubj,
+}) => {
const { euiTheme } = useEuiTheme();
return (
@@ -33,6 +40,7 @@ export const NavigationUI: FC = ({ children, unstyled, footerChildren, ho
gutterSize="none"
style={{ overflowY: 'auto' }}
justifyContent="spaceBetween"
+ data-test-subj={dataTestSubj}
>
{children}
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/components/recently_accessed.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/recently_accessed.tsx
similarity index 88%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/components/recently_accessed.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/components/recently_accessed.tsx
index 0c5b0b0ead1fe..b6bbff8904ed4 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/components/recently_accessed.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/components/recently_accessed.tsx
@@ -11,11 +11,11 @@ import React, { FC } from 'react';
import useObservable from 'react-use/lib/useObservable';
import type { Observable } from 'rxjs';
-import { RecentItem } from '../../../../types/internal';
-import { useNavigation as useServices } from '../../../services';
-import { navigationStyles as styles } from '../../../styles';
+import { RecentItem } from '../../../types/internal';
+import { useNavigation as useServices } from '../../services';
+import { navigationStyles as styles } from '../../styles';
-import { getI18nStrings } from '../../i18n_strings';
+import { getI18nStrings } from '../i18n_strings';
export interface Props {
recentlyAccessed$?: Observable;
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.helpers.ts b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.helpers.ts
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.helpers.ts
rename to packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.helpers.ts
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.test.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.tsx
similarity index 95%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.tsx
rename to packages/shared-ux/chrome/navigation/src/ui/default_navigation.tsx
index e1a34b5e5092d..ec2b921e01e82 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/default_navigation.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.tsx
@@ -69,10 +69,11 @@ const getDefaultNavigationTree = (
let idCounter = 0;
-export const DefaultNavigation: FC = ({
+export const DefaultNavigation: FC = ({
homeRef,
projectNavigationTree,
navigationTree,
+ dataTestSubj,
}) => {
if (!navigationTree && !projectNavigationTree) {
throw new Error('One of navigationTree or projectNavigationTree must be defined');
@@ -130,7 +131,7 @@ export const DefaultNavigation: FC = ({
);
return (
-
+
<>
{renderItems(navigationDefinition.body)}
{navigationDefinition.footer && (
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/hooks/index.ts b/packages/shared-ux/chrome/navigation/src/ui/hooks/index.ts
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/hooks/index.ts
rename to packages/shared-ux/chrome/navigation/src/ui/hooks/index.ts
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/hooks/use_init_navnode.ts b/packages/shared-ux/chrome/navigation/src/ui/hooks/use_init_navnode.ts
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/hooks/use_init_navnode.ts
rename to packages/shared-ux/chrome/navigation/src/ui/hooks/use_init_navnode.ts
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/hooks/use_register_tree_node.ts b/packages/shared-ux/chrome/navigation/src/ui/hooks/use_register_tree_node.ts
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/hooks/use_register_tree_node.ts
rename to packages/shared-ux/chrome/navigation/src/ui/hooks/use_register_tree_node.ts
diff --git a/packages/shared-ux/chrome/navigation/src/ui/index.ts b/packages/shared-ux/chrome/navigation/src/ui/index.ts
new file mode 100644
index 0000000000000..34089f5ad633b
--- /dev/null
+++ b/packages/shared-ux/chrome/navigation/src/ui/index.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { Navigation } from './components';
+
+export { DefaultNavigation } from './default_navigation';
+
+export type {
+ NavigationTreeDefinition,
+ ProjectNavigationDefinition,
+ NodeDefinition,
+ NavigationGroupPreset,
+ GroupDefinition,
+ RecentlyAccessedDefinition,
+ CloudLinkDefinition,
+ RootNavigationItemDefinition,
+} from './types';
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/analytics.ts b/packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/analytics.ts
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/analytics.ts
rename to packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/analytics.ts
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/devtools.ts b/packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/devtools.ts
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/devtools.ts
rename to packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/devtools.ts
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/index.ts b/packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/index.ts
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/index.ts
rename to packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/index.ts
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/management.ts b/packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/management.ts
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/management.ts
rename to packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/management.ts
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/ml.ts b/packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/ml.ts
similarity index 100%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/nav_tree_presets/ml.ts
rename to packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/ml.ts
diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx
index b666f4d409869..987d6d3dda8f9 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx
@@ -6,39 +6,41 @@
* Side Public License, v 1.
*/
+import React, { FC, useCallback, useState } from 'react';
+import { of } from 'rxjs';
+import { ComponentMeta } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+import type { ChromeNavLink } from '@kbn/core-chrome-browser';
+
import {
- EuiButtonEmpty,
+ EuiButton,
EuiButtonIcon,
EuiCollapsibleNav,
- EuiPopover,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLink,
+ EuiText,
EuiThemeProvider,
+ EuiTitle,
} from '@elastic/eui';
import { css } from '@emotion/react';
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import React, { useCallback, useState } from 'react';
-import { BehaviorSubject } from 'rxjs';
-import { getSolutionPropertiesMock, NavigationStorybookMock } from '../../mocks';
-import mdx from '../../README.mdx';
-import { ChromeNavigationViewModel, NavigationServices } from '../../types';
-import { Platform } from '../model';
-import { NavigationProvider } from '../services';
-import { Navigation as Component } from './navigation';
+import { NavigationStorybookMock } from '../../../mocks';
+import mdx from '../../../README.mdx';
+import { NavigationProvider } from '../../services';
+import { DefaultNavigation } from './default_navigation';
+import type { ChromeNavigationViewModel, NavigationServices } from '../../../types';
+import { Navigation } from './components';
+import { ProjectNavigationDefinition } from './types';
+import { getPresets } from './nav_tree_presets';
const storybookMock = new NavigationStorybookMock();
const SIZE_OPEN = 248;
const SIZE_CLOSED = 40;
-const Template = (args: ChromeNavigationViewModel & NavigationServices) => {
- const services = storybookMock.getServices(args);
- const props = storybookMock.getProps(args);
-
+const NavigationWrapper: FC = ({ children }) => {
const [isOpen, setIsOpen] = useState(true);
- const toggleOpen = useCallback(() => {
- setIsOpen(!isOpen);
- }, [isOpen, setIsOpen]);
-
const collabsibleNavCSS = css`
border-inline-end-width: 1,
display: flex,
@@ -57,11 +59,16 @@ const Template = (args: ChromeNavigationViewModel & NavigationServices) => {
iconType={isOpen ? 'menuLeft' : 'menuRight'}
color={isOpen ? 'ghost' : 'text'}
onClick={toggleOpen}
+ aria-label={isOpen ? 'Collapse navigation' : 'Expand navigation'}
/>
);
};
+ const toggleOpen = useCallback(() => {
+ setIsOpen(!isOpen);
+ }, [isOpen, setIsOpen]);
+
return (
{
hideCloseButton={false}
button={}
>
- {isOpen && (
-
-
-
- )}
+ {isOpen && children}
);
};
-export default {
- title: 'Chrome/Navigation',
- description: 'Navigation container to render items for cross-app linking',
- parameters: {
- docs: {
- page: mdx,
- },
- },
- component: Template,
-} as ComponentMeta;
+const baseDeeplink: ChromeNavLink = {
+ id: 'foo',
+ title: 'Title from deep link',
+ href: 'https://elastic.co',
+ url: '',
+ baseUrl: '',
+};
-export const SingleExpanded: ComponentStory = Template.bind({});
-SingleExpanded.args = {
- activeNavItemId: 'example_project.root.get_started',
- navigationTree: [getSolutionPropertiesMock()],
+const createDeepLink = (id: string, title: string = baseDeeplink.title) => {
+ return {
+ ...baseDeeplink,
+ id,
+ title,
+ };
};
-SingleExpanded.argTypes = storybookMock.getArgumentTypes();
-
-export const ReducedPlatformLinks: ComponentStory = Template.bind({});
-ReducedPlatformLinks.args = {
- activeNavItemId: 'example_project.root.get_started',
- platformConfig: {
- [Platform.Analytics]: { enabled: false },
- [Platform.MachineLearning]: { enabled: false },
- [Platform.DevTools]: { enabled: false },
- [Platform.Management]: {
- properties: {
- root: {
- enabled: false, // disables the un-named section that contains only "Stack Monitoring"
- },
- integration_management: {
- properties: {
- integrations: { enabled: false }, // enable only osquery
- fleet: { enabled: false }, // enable only osquery
- },
+
+const deepLinks: ChromeNavLink[] = [
+ createDeepLink('item1'),
+ createDeepLink('item2', 'Foo'),
+ createDeepLink('group1:item1'),
+ createDeepLink('group1:groupA:groupI:item1'),
+ createDeepLink('group1:groupA', 'Group title from deep link'),
+ createDeepLink('group2', 'Group title from deep link'),
+ createDeepLink('group2:item1'),
+ createDeepLink('group2:item3'),
+];
+
+const simpleNavigationDefinition: ProjectNavigationDefinition = {
+ homeRef: 'https://elastic.co',
+ projectNavigationTree: [
+ {
+ id: 'example_projet',
+ title: 'Example project',
+ icon: 'logoObservability',
+ defaultIsCollapsed: false,
+ children: [
+ {
+ id: 'root',
+ children: [
+ {
+ id: 'item1',
+ title: 'Get started',
+ },
+ {
+ id: 'item2',
+ title: 'Alerts',
+ },
+ {
+ id: 'item3',
+ title: 'Dashboards',
+ },
+ ],
},
- stack_management: {
- enabled: false, // disables the stack management section
+ {
+ id: 'group:settings',
+ title: 'Settings',
+ children: [
+ {
+ id: 'logs',
+ title: 'Logs',
+ },
+ {
+ id: 'signals',
+ title: 'Signals',
+ },
+ {
+ id: 'tracing',
+ title: 'Tracing',
+ },
+ ],
},
- },
+ ],
+ },
+ ],
+};
+
+export const SimpleObjectDefinition = (args: ChromeNavigationViewModel & NavigationServices) => {
+ const services = storybookMock.getServices({
+ ...args,
+ navLinks$: of(deepLinks),
+ onProjectNavigationChange: (updated) => {
+ action('Update chrome navigation')(JSON.stringify(updated, null, 2));
},
+ recentlyAccessed$: of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]),
+ });
+
+ return (
+
+
+
+
+
+ );
+};
+
+const navigationDefinition: ProjectNavigationDefinition = {
+ homeRef: 'https://elastic.co',
+ navigationTree: {
+ body: [
+ {
+ type: 'cloudLink',
+ preset: 'deployments',
+ },
+ // My custom project
+ {
+ type: 'navGroup',
+ id: 'example_projet',
+ title: 'Example project',
+ icon: 'logoObservability',
+ defaultIsCollapsed: false,
+ children: [
+ {
+ id: 'root',
+ children: [
+ {
+ id: 'item1',
+ title: 'Get started',
+ },
+ {
+ id: 'item2',
+ title: 'Alerts',
+ },
+ {
+ id: 'item3',
+ title: 'Some other node',
+ },
+ ],
+ },
+ {
+ id: 'group:settings',
+ title: 'Settings',
+ children: [
+ {
+ id: 'logs',
+ title: 'Logs',
+ },
+ {
+ id: 'signals',
+ title: 'Signals',
+ },
+ {
+ id: 'tracing',
+ title: 'Tracing',
+ },
+ ],
+ },
+ ],
+ },
+ // Add ml
+ {
+ type: 'navGroup',
+ preset: 'ml',
+ },
+ // And specific links from analytics
+ {
+ type: 'navGroup',
+ ...getPresets('analytics'),
+ title: 'My analytics', // Change the title
+ children: getPresets('analytics').children.map((child) => ({
+ ...child,
+ children: child.children?.filter((item) => {
+ // Hide discover and dashboard
+ return item.id !== 'discover' && item.id !== 'dashboard';
+ }),
+ })),
+ },
+ ],
+ footer: [
+ {
+ type: 'recentlyAccessed',
+ defaultIsCollapsed: true,
+ // Override the default recently accessed items with our own
+ recentlyAccessed$: of([
+ {
+ label: 'My own recent item',
+ id: '1234',
+ link: '/app/example/39859',
+ },
+ {
+ label: 'I also own this',
+ id: '4567',
+ link: '/app/example/39859',
+ },
+ ]),
+ },
+ {
+ type: 'navGroup',
+ ...getPresets('devtools'),
+ },
+ ],
},
- navigationTree: [getSolutionPropertiesMock()],
};
-ReducedPlatformLinks.argTypes = storybookMock.getArgumentTypes();
-export const WithRequestsLoading: ComponentStory = Template.bind({});
-WithRequestsLoading.args = {
- activeNavItemId: 'example_project.root.get_started',
- loadingCount$: new BehaviorSubject(1),
- navigationTree: [getSolutionPropertiesMock()],
+export const ComplexObjectDefinition = (args: ChromeNavigationViewModel & NavigationServices) => {
+ const services = storybookMock.getServices({
+ ...args,
+ navLinks$: of(deepLinks),
+ onProjectNavigationChange: (updated) => {
+ action('Update chrome navigation')(JSON.stringify(updated, null, 2));
+ },
+ recentlyAccessed$: of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]),
+ });
+
+ return (
+
+
+
+
+
+ );
};
-WithRequestsLoading.argTypes = storybookMock.getArgumentTypes();
-
-export const WithRecentlyAccessed: ComponentStory = Template.bind({});
-WithRecentlyAccessed.args = {
- activeNavItemId: 'example_project.root.get_started',
- loadingCount$: new BehaviorSubject(0),
- recentlyAccessed$: new BehaviorSubject([
- { label: 'This is an example', link: '/app/example/39859', id: '39850' },
- { label: 'This is not an example', link: '/app/non-example/39458', id: '39458' }, // NOTE: this will be filtered out
- ]),
- recentlyAccessedFilter: (items) =>
- items.filter((item) => item.link.indexOf('/app/example') === 0),
- navigationTree: [getSolutionPropertiesMock()],
+
+export const WithUIComponents = (args: ChromeNavigationViewModel & NavigationServices) => {
+ const services = storybookMock.getServices({
+ ...args,
+ navLinks$: of(deepLinks),
+ onProjectNavigationChange: (updated) => {
+ action('Update chrome navigation')(JSON.stringify(updated, null, 2));
+ },
+ recentlyAccessed$: of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]),
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {(navNode) => {
+ return (
+ {`Render prop: ${navNode.id} - ${navNode.title}`}
+ );
+ }}
+
+
+
+ Title in ReactNode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
};
-WithRecentlyAccessed.argTypes = storybookMock.getArgumentTypes();
-export const CustomElements: ComponentStory = Template.bind({});
-CustomElements.args = {
- activeNavItemId: 'example_project.custom',
- navigationTree: [
- {
- ...getSolutionPropertiesMock(),
- items: [
- {
- title: (
-
- Custom element
-
- }
- isOpen={true}
- anchorPosition="rightCenter"
- >
- Cool popover content
-
- ),
- id: 'custom',
- },
- ],
+export const MinimalUIAndCustomCloudLink = (
+ args: ChromeNavigationViewModel & NavigationServices
+) => {
+ const services = storybookMock.getServices({
+ ...args,
+ navLinks$: of(deepLinks),
+ onProjectNavigationChange: (updated) => {
+ action('Update chrome navigation')(JSON.stringify(updated, null, 2));
},
- ],
+ recentlyAccessed$: of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]),
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some children node
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default {
+ title: 'Chrome/Navigation/v2',
+ description: 'Navigation container to render items for cross-app linking',
+ parameters: {
+ docs: {
+ page: mdx,
+ },
+ },
+ component: WithUIComponents,
+} as ComponentMeta;
+
+export const CreativeUI = (args: ChromeNavigationViewModel & NavigationServices) => {
+ const services = storybookMock.getServices({
+ ...args,
+ navLinks$: of(deepLinks),
+ onProjectNavigationChange: (updated) => {
+ action('Update chrome navigation')(JSON.stringify(updated, null, 2));
+ },
+ recentlyAccessed$: of([
+ { label: 'This is an example', link: '/app/example/39859', id: '39850' },
+ { label: 'Another example', link: '/app/example/5235', id: '5235' },
+ ]),
+ });
+
+ return (
+
+
+
+
+
+
+
+
+ Hello!
+
+
+
+
+
+
+ As you can see there is really no limit in what UI you can create!
+
+
+
+ Have fun!
+
+
+
+
+
+
+
+
+
+
+
+
+ );
};
-CustomElements.argTypes = storybookMock.getArgumentTypes();
diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx
deleted file mode 100644
index 4a39a4e651d7a..0000000000000
--- a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { render } from '@testing-library/react';
-import React from 'react';
-import { BehaviorSubject } from 'rxjs';
-import { getServicesMock } from '../../mocks/src/jest';
-import { ChromeNavigationNodeViewModel, PlatformConfigSet } from '../../types';
-import { Platform } from '../model';
-import { NavigationProvider } from '../services';
-import { Navigation } from './navigation';
-
-describe('', () => {
- const services = getServicesMock();
-
- const homeHref = '#';
- let platformSections: PlatformConfigSet | undefined;
- let solutions: ChromeNavigationNodeViewModel[];
-
- beforeEach(() => {
- platformSections = { analytics: {}, ml: {}, devTools: {}, management: {} };
- solutions = [{ id: 'navigation_testing', title: 'Navigation testing', icon: 'gear' }];
- });
-
- test('renders the header logo and top-level navigation buckets', async () => {
- const { findByTestId, findByText, queryByTestId } = render(
-
-
-
- );
-
- expect(await findByText('Navigation testing')).toBeVisible();
-
- expect(await findByTestId('nav-header-logo')).toBeVisible();
- expect(await findByTestId('nav-bucket-navigation_testing')).toBeVisible();
- expect(await findByTestId('nav-bucket-analytics')).toBeVisible();
- expect(await findByTestId('nav-bucket-ml')).toBeVisible();
- expect(await findByTestId('nav-bucket-devTools')).toBeVisible();
- expect(await findByTestId('nav-bucket-management')).toBeVisible();
-
- expect(queryByTestId('nav-bucket-recentlyAccessed')).not.toBeInTheDocument();
- });
-
- test('includes link to deployments', async () => {
- const { findByText } = render(
-
-
-
- );
-
- expect(await findByText('My deployments')).toBeVisible();
- });
-
- test('platform links can be disabled', async () => {
- platformSections = {
- [Platform.Analytics]: { enabled: false },
- [Platform.MachineLearning]: { enabled: false },
- [Platform.DevTools]: { enabled: false },
- [Platform.Management]: { enabled: false },
- };
-
- const { findByTestId, queryByTestId } = render(
-
-
-
- );
-
- expect(await findByTestId('nav-header-logo')).toBeVisible();
- expect(queryByTestId('nav-bucket-analytics')).not.toBeInTheDocument();
- expect(queryByTestId('nav-bucket-ml')).not.toBeInTheDocument();
- expect(queryByTestId('nav-bucket-devTools')).not.toBeInTheDocument();
- expect(queryByTestId('nav-bucket-management')).not.toBeInTheDocument();
- });
-
- test('sets the specified nav item to active', async () => {
- solutions[0].items = [
- {
- id: 'root',
- title: '',
- items: [
- {
- id: 'city',
- title: 'City',
- },
- {
- id: 'town',
- title: 'Town',
- },
- ],
- },
- ];
-
- const { findByTestId } = render(
-
-
-
- );
-
- const label = await findByTestId('nav-item-navigation_testing.root.city-selected');
- expect(label).toHaveTextContent('City');
- expect(label).toBeVisible();
- });
-
- test('shows loading state', async () => {
- services.loadingCount$ = new BehaviorSubject(5);
-
- const { findByTestId } = render(
-
-
-
- );
-
- expect(await findByTestId('nav-header-loading-spinner')).toBeVisible();
- });
-
- describe('recent items', () => {
- const recentlyAccessed = [
- { id: 'dashboard:234', label: 'Recently Accessed Test Item', link: '/app/dashboard/234' },
- ];
-
- test('shows recent items', async () => {
- services.recentlyAccessed$ = new BehaviorSubject(recentlyAccessed);
-
- const { findByTestId } = render(
-
-
-
- );
-
- expect(await findByTestId('nav-bucket-recentlyAccessed')).toBeVisible();
- });
-
- test('shows no recent items container when items are filtered', async () => {
- services.recentlyAccessed$ = new BehaviorSubject(recentlyAccessed);
-
- const { queryByTestId } = render(
-
- []}
- />
-
- );
-
- expect(queryByTestId('nav-bucket-recentlyAccessed')).not.toBeInTheDocument();
- });
- });
-});
diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx
deleted file mode 100644
index 05407fd0b1a7b..0000000000000
--- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import {
- EuiCollapsibleNavGroup,
- EuiFlexGroup,
- EuiFlexItem,
- EuiHeaderLogo,
- EuiLink,
- EuiLoadingSpinner,
- EuiSideNav,
- EuiSideNavItemType,
- EuiSpacer,
- useEuiTheme,
-} from '@elastic/eui';
-import React from 'react';
-import useObservable from 'react-use/lib/useObservable';
-import type { ChromeNavigationViewModel } from '../../types';
-import { NavigationModel } from '../model';
-import { useNavigation } from '../services';
-import { navigationStyles as styles } from '../styles';
-import { ElasticMark } from './elastic_mark';
-import './header_logo.scss';
-import { getI18nStrings } from './i18n_strings';
-import { NavigationBucket, type Props as NavigationBucketProps } from './navigation_bucket';
-
-interface Props extends ChromeNavigationViewModel {
- /**
- * ID of sections to highlight
- */
- activeNavItemId?: string;
- dataTestSubj?: string; // optional test subject for the navigation
-}
-
-export const Navigation = ({
- platformConfig,
- navigationTree,
- homeHref,
- linkToCloud,
- activeNavItemId: activeNavItemIdProps,
- ...props
-}: Props) => {
- const { activeNavItemId } = useNavigation();
- const { euiTheme } = useEuiTheme();
-
- const activeNav = activeNavItemId ?? activeNavItemIdProps;
-
- const nav = new NavigationModel(platformConfig, navigationTree);
-
- const solutions = nav.getSolutions();
- const { analytics, ml, devTools, management } = nav.getPlatform();
-
- const strings = getI18nStrings();
-
- const NavHeader = () => {
- const { basePath, navIsOpen, navigateToUrl, loadingCount$ } = useNavigation();
- const loadingCount = useObservable(loadingCount$, 0);
- const homeUrl = basePath.prepend(homeHref);
- const navigateHome = (event: React.MouseEvent) => {
- event.preventDefault();
- navigateToUrl(homeUrl);
- };
- const logo =
- loadingCount === 0 ? (
-
- ) : (
-
-
-
- );
-
- return (
- <>
- {logo}
- {navIsOpen ? : null}
- >
- );
- };
-
- const LinkToCloud = () => {
- switch (linkToCloud) {
- case 'projects':
- return (
-
-
-
- );
- case 'deployments':
- return (
-
-
-
- );
- default:
- return null;
- }
- };
-
- const RecentlyAccessed = () => {
- const { recentlyAccessed$ } = useNavigation();
- const recentlyAccessed = useObservable(recentlyAccessed$, []);
-
- // consumer may filter objects from recent that are not applicable to the project
- let filteredRecent = recentlyAccessed;
- if (props.recentlyAccessedFilter) {
- filteredRecent = props.recentlyAccessedFilter(recentlyAccessed);
- }
-
- if (filteredRecent.length > 0) {
- const navItems: Array> = [
- {
- name: '', // no list header title
- id: 'recents_root',
- items: filteredRecent.map(({ id, label, link }) => ({
- id,
- name: label,
- href: link,
- })),
- },
- ];
-
- return (
-
-
-
- );
- }
-
- return null;
- };
-
- // higher-order-component to keep the common props DRY
- const NavigationBucketHoc = (outerProps: Omit) => (
-
- );
-
- return (
-
-
-
-
-
-
-
-
-
-
- {solutions.map((navTree, idx) => {
- return ;
- })}
-
- {nav.isEnabled('analytics') ? : null}
- {nav.isEnabled('ml') ? : null}
-
-
-
-
-
-
-
- {nav.isEnabled('devTools') ? : null}
- {nav.isEnabled('management') ? : null}
-
-
- );
-};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx
deleted file mode 100644
index 8055daf834a64..0000000000000
--- a/packages/shared-ux/chrome/navigation/src/ui/navigation_bucket.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import {
- EuiCollapsibleNavGroup,
- EuiIcon,
- EuiSideNav,
- EuiSideNavItemType,
- EuiText,
-} from '@elastic/eui';
-import React from 'react';
-import { ChromeNavigationNodeViewModel } from '../../types';
-import type { BasePathService, NavigateToUrlFn } from '../../types/internal';
-import { useNavigation } from '../services';
-import { navigationStyles as styles } from '../styles';
-
-const navigationNodeToEuiItem = (
- item: ChromeNavigationNodeViewModel,
- {
- navigateToUrl,
- basePath,
- activeNavItemId,
- }: { activeNavItemId?: string; navigateToUrl: NavigateToUrlFn; basePath: BasePathService }
-): EuiSideNavItemType => {
- const path = item.path ?? item.id;
-
- let subjId = path;
- let isSelected: boolean = false;
-
- if (!item.items && path === activeNavItemId) {
- // if there are no subnav items and ID is current, mark the item as selected
- isSelected = true;
- subjId += '-selected';
- }
-
- return {
- id: path,
- name: item.title,
- isSelected,
- onClick:
- item.href !== undefined
- ? (event: React.MouseEvent) => {
- event.preventDefault();
- navigateToUrl(basePath.prepend(item.href!));
- }
- : undefined,
- href: item.href,
- items: item.items?.map((_item) =>
- navigationNodeToEuiItem(_item, { navigateToUrl, basePath, activeNavItemId })
- ),
- ['data-test-subj']: `nav-item-${subjId}`,
- ...(item.icon && {
- icon: ,
- }),
- };
-};
-
-export interface Props {
- navigationTree: ChromeNavigationNodeViewModel;
- activeNavItemId?: string;
-}
-
-export const NavigationBucket = (props: Props) => {
- const { navigationTree, activeNavItemId } = props;
- const { navIsOpen, navigateToUrl, basePath } = useNavigation();
- const { id, title, icon, items } = navigationTree;
-
- if (navIsOpen) {
- return (
-
-
-
- navigationNodeToEuiItem(item, { navigateToUrl, basePath, activeNavItemId })
- )}
- css={styles.euiSideNavItems}
- />
-
-
- );
- }
-
- return (
-
-
-
-
- );
-};
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/types.ts b/packages/shared-ux/chrome/navigation/src/ui/types.ts
similarity index 99%
rename from packages/shared-ux/chrome/navigation/src/ui/v2/types.ts
rename to packages/shared-ux/chrome/navigation/src/ui/types.ts
index e04f8af8bdf67..96c283d508f87 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/types.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/types.ts
@@ -54,7 +54,7 @@ export interface NodeProps extends Omit {
}
/**
- * @private
+ * @internal
*
* Internally we enhance the Props passed to the Navigation.Item component.
*/
@@ -66,6 +66,9 @@ export interface NodePropsEnhanced extends NodeProps {
renderItem?: () => ReactElement;
}
+/**
+ * @internal
+ */
export interface ChromeProjectNavigationNodeEnhanced extends ChromeProjectNavigationNode {
/**
* This function correspond to the same "itemRender" function that can be passed to
@@ -164,7 +167,7 @@ export interface ProjectNavigationDefinition {
}
/**
- * @private
+ * @internal
*
* Function to unregister a navigation node from its parent.
*/
diff --git a/packages/shared-ux/chrome/navigation/src/ui/v2/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/v2/navigation.stories.tsx
deleted file mode 100644
index 987d6d3dda8f9..0000000000000
--- a/packages/shared-ux/chrome/navigation/src/ui/v2/navigation.stories.tsx
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import React, { FC, useCallback, useState } from 'react';
-import { of } from 'rxjs';
-import { ComponentMeta } from '@storybook/react';
-import { action } from '@storybook/addon-actions';
-import type { ChromeNavLink } from '@kbn/core-chrome-browser';
-
-import {
- EuiButton,
- EuiButtonIcon,
- EuiCollapsibleNav,
- EuiFlexGroup,
- EuiFlexItem,
- EuiLink,
- EuiText,
- EuiThemeProvider,
- EuiTitle,
-} from '@elastic/eui';
-import { css } from '@emotion/react';
-import { NavigationStorybookMock } from '../../../mocks';
-import mdx from '../../../README.mdx';
-import { NavigationProvider } from '../../services';
-import { DefaultNavigation } from './default_navigation';
-import type { ChromeNavigationViewModel, NavigationServices } from '../../../types';
-import { Navigation } from './components';
-import { ProjectNavigationDefinition } from './types';
-import { getPresets } from './nav_tree_presets';
-
-const storybookMock = new NavigationStorybookMock();
-
-const SIZE_OPEN = 248;
-const SIZE_CLOSED = 40;
-
-const NavigationWrapper: FC = ({ children }) => {
- const [isOpen, setIsOpen] = useState(true);
-
- const collabsibleNavCSS = css`
- border-inline-end-width: 1,
- display: flex,
- flex-direction: row,
- `;
-
- const CollapseButton = () => {
- const buttonCSS = css`
- margin-left: -32px;
- position: fixed;
- z-index: 1000;
- `;
- return (
-
-
-
- );
- };
-
- const toggleOpen = useCallback(() => {
- setIsOpen(!isOpen);
- }, [isOpen, setIsOpen]);
-
- return (
-
- }
- >
- {isOpen && children}
-
-
- );
-};
-
-const baseDeeplink: ChromeNavLink = {
- id: 'foo',
- title: 'Title from deep link',
- href: 'https://elastic.co',
- url: '',
- baseUrl: '',
-};
-
-const createDeepLink = (id: string, title: string = baseDeeplink.title) => {
- return {
- ...baseDeeplink,
- id,
- title,
- };
-};
-
-const deepLinks: ChromeNavLink[] = [
- createDeepLink('item1'),
- createDeepLink('item2', 'Foo'),
- createDeepLink('group1:item1'),
- createDeepLink('group1:groupA:groupI:item1'),
- createDeepLink('group1:groupA', 'Group title from deep link'),
- createDeepLink('group2', 'Group title from deep link'),
- createDeepLink('group2:item1'),
- createDeepLink('group2:item3'),
-];
-
-const simpleNavigationDefinition: ProjectNavigationDefinition = {
- homeRef: 'https://elastic.co',
- projectNavigationTree: [
- {
- id: 'example_projet',
- title: 'Example project',
- icon: 'logoObservability',
- defaultIsCollapsed: false,
- children: [
- {
- id: 'root',
- children: [
- {
- id: 'item1',
- title: 'Get started',
- },
- {
- id: 'item2',
- title: 'Alerts',
- },
- {
- id: 'item3',
- title: 'Dashboards',
- },
- ],
- },
- {
- id: 'group:settings',
- title: 'Settings',
- children: [
- {
- id: 'logs',
- title: 'Logs',
- },
- {
- id: 'signals',
- title: 'Signals',
- },
- {
- id: 'tracing',
- title: 'Tracing',
- },
- ],
- },
- ],
- },
- ],
-};
-
-export const SimpleObjectDefinition = (args: ChromeNavigationViewModel & NavigationServices) => {
- const services = storybookMock.getServices({
- ...args,
- navLinks$: of(deepLinks),
- onProjectNavigationChange: (updated) => {
- action('Update chrome navigation')(JSON.stringify(updated, null, 2));
- },
- recentlyAccessed$: of([
- { label: 'This is an example', link: '/app/example/39859', id: '39850' },
- { label: 'Another example', link: '/app/example/5235', id: '5235' },
- ]),
- });
-
- return (
-
-
-
-
-
- );
-};
-
-const navigationDefinition: ProjectNavigationDefinition = {
- homeRef: 'https://elastic.co',
- navigationTree: {
- body: [
- {
- type: 'cloudLink',
- preset: 'deployments',
- },
- // My custom project
- {
- type: 'navGroup',
- id: 'example_projet',
- title: 'Example project',
- icon: 'logoObservability',
- defaultIsCollapsed: false,
- children: [
- {
- id: 'root',
- children: [
- {
- id: 'item1',
- title: 'Get started',
- },
- {
- id: 'item2',
- title: 'Alerts',
- },
- {
- id: 'item3',
- title: 'Some other node',
- },
- ],
- },
- {
- id: 'group:settings',
- title: 'Settings',
- children: [
- {
- id: 'logs',
- title: 'Logs',
- },
- {
- id: 'signals',
- title: 'Signals',
- },
- {
- id: 'tracing',
- title: 'Tracing',
- },
- ],
- },
- ],
- },
- // Add ml
- {
- type: 'navGroup',
- preset: 'ml',
- },
- // And specific links from analytics
- {
- type: 'navGroup',
- ...getPresets('analytics'),
- title: 'My analytics', // Change the title
- children: getPresets('analytics').children.map((child) => ({
- ...child,
- children: child.children?.filter((item) => {
- // Hide discover and dashboard
- return item.id !== 'discover' && item.id !== 'dashboard';
- }),
- })),
- },
- ],
- footer: [
- {
- type: 'recentlyAccessed',
- defaultIsCollapsed: true,
- // Override the default recently accessed items with our own
- recentlyAccessed$: of([
- {
- label: 'My own recent item',
- id: '1234',
- link: '/app/example/39859',
- },
- {
- label: 'I also own this',
- id: '4567',
- link: '/app/example/39859',
- },
- ]),
- },
- {
- type: 'navGroup',
- ...getPresets('devtools'),
- },
- ],
- },
-};
-
-export const ComplexObjectDefinition = (args: ChromeNavigationViewModel & NavigationServices) => {
- const services = storybookMock.getServices({
- ...args,
- navLinks$: of(deepLinks),
- onProjectNavigationChange: (updated) => {
- action('Update chrome navigation')(JSON.stringify(updated, null, 2));
- },
- recentlyAccessed$: of([
- { label: 'This is an example', link: '/app/example/39859', id: '39850' },
- { label: 'Another example', link: '/app/example/5235', id: '5235' },
- ]),
- });
-
- return (
-
-
-
-
-
- );
-};
-
-export const WithUIComponents = (args: ChromeNavigationViewModel & NavigationServices) => {
- const services = storybookMock.getServices({
- ...args,
- navLinks$: of(deepLinks),
- onProjectNavigationChange: (updated) => {
- action('Update chrome navigation')(JSON.stringify(updated, null, 2));
- },
- recentlyAccessed$: of([
- { label: 'This is an example', link: '/app/example/39859', id: '39850' },
- { label: 'Another example', link: '/app/example/5235', id: '5235' },
- ]),
- });
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- {(navNode) => {
- return (
- {`Render prop: ${navNode.id} - ${navNode.title}`}
- );
- }}
-
-
-
- Title in ReactNode
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export const MinimalUIAndCustomCloudLink = (
- args: ChromeNavigationViewModel & NavigationServices
-) => {
- const services = storybookMock.getServices({
- ...args,
- navLinks$: of(deepLinks),
- onProjectNavigationChange: (updated) => {
- action('Update chrome navigation')(JSON.stringify(updated, null, 2));
- },
- recentlyAccessed$: of([
- { label: 'This is an example', link: '/app/example/39859', id: '39850' },
- { label: 'Another example', link: '/app/example/5235', id: '5235' },
- ]),
- });
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- Some children node
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default {
- title: 'Chrome/Navigation/v2',
- description: 'Navigation container to render items for cross-app linking',
- parameters: {
- docs: {
- page: mdx,
- },
- },
- component: WithUIComponents,
-} as ComponentMeta;
-
-export const CreativeUI = (args: ChromeNavigationViewModel & NavigationServices) => {
- const services = storybookMock.getServices({
- ...args,
- navLinks$: of(deepLinks),
- onProjectNavigationChange: (updated) => {
- action('Update chrome navigation')(JSON.stringify(updated, null, 2));
- },
- recentlyAccessed$: of([
- { label: 'This is an example', link: '/app/example/39859', id: '39850' },
- { label: 'Another example', link: '/app/example/5235', id: '5235' },
- ]),
- });
-
- return (
-
-
-
-
-
-
-
-
- Hello!
-
-
-
-
-
-
- As you can see there is really no limit in what UI you can create!
-
-
-
- Have fun!
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
From 71d43427298cb0a6bd921a55fdc647544fb2dc1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Tue, 30 May 2023 16:17:27 +0100
Subject: [PATCH 14/18] Update serverless search
---
.../serverless_search/public/layout/nav.tsx | 282 +++++++++---------
1 file changed, 144 insertions(+), 138 deletions(-)
diff --git a/x-pack/plugins/serverless_search/public/layout/nav.tsx b/x-pack/plugins/serverless_search/public/layout/nav.tsx
index fb032eb19cbdb..cf01f2400b370 100644
--- a/x-pack/plugins/serverless_search/public/layout/nav.tsx
+++ b/x-pack/plugins/serverless_search/public/layout/nav.tsx
@@ -7,157 +7,163 @@
import { CoreStart } from '@kbn/core/public';
import {
- ChromeNavigationNodeViewModel,
- Navigation,
+ DefaultNavigation,
NavigationKibanaProvider,
+ NavigationTreeDefinition,
} from '@kbn/shared-ux-chrome-navigation';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { ServerlessPluginStart } from '@kbn/serverless/public';
-const NAVIGATION_PLATFORM_CONFIG = {
- analytics: { enabled: false },
- ml: { enabled: false },
- devTools: { enabled: false },
- management: { enabled: false },
+const navigationTree: NavigationTreeDefinition = {
+ body: [
+ {
+ type: 'cloudLink',
+ preset: 'projects',
+ },
+ {
+ type: 'navGroup',
+ id: 'search_project_nav',
+ title: 'Elasticsearch',
+ icon: 'logoElasticsearch',
+ children: [
+ {
+ id: 'root',
+ children: [
+ {
+ id: 'search_getting_started',
+ title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', {
+ defaultMessage: 'Getting started',
+ }),
+ href: '/app/elasticsearch',
+ },
+ {
+ id: 'dev_tools',
+ title: i18n.translate('xpack.serverlessSearch.nav.devTools', {
+ defaultMessage: 'Dev Tools',
+ }),
+ children: [
+ {
+ id: 'dev_tools_console',
+ title: i18n.translate('xpack.serverlessSearch.nav.devTools.console', {
+ defaultMessage: 'Console',
+ }),
+ href: '/app/dev_tools#/console',
+ },
+ {
+ id: 'dev_tools_profiler',
+ title: i18n.translate('xpack.serverlessSearch.nav.devTools.searchProfiler', {
+ defaultMessage: 'Search Profiler',
+ }),
+ href: '/app/dev_tools#/searchprofiler',
+ },
+ {
+ id: 'dev_tools_grok_debugger',
+ title: i18n.translate('xpack.serverlessSearch.nav.devTools.grokDebugger', {
+ defaultMessage: 'Grok debugger',
+ }),
+ href: '/app/dev_tools#/grokdebugger',
+ },
+ {
+ id: 'dev_tools_painless_lab',
+ title: i18n.translate('xpack.serverlessSearch.nav.devTools.painlessLab', {
+ defaultMessage: 'Painless Lab',
+ }),
+ href: '/app/dev_tools#/painless_lab',
+ },
+ ],
+ },
+ {
+ id: 'explore',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore', {
+ defaultMessage: 'Explore',
+ }),
+ children: [
+ {
+ id: 'explore_discover',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore.discover', {
+ defaultMessage: 'Discover',
+ }),
+ href: '/app/discover',
+ },
+ {
+ id: 'explore_dashboard',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore.dashboard', {
+ defaultMessage: 'Dashboard',
+ }),
+ href: '/app/dashboards',
+ },
+ {
+ id: 'explore_visualize_library',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore.visualizeLibrary', {
+ defaultMessage: 'Visualize Library',
+ }),
+ href: '/app/visualize',
+ },
+ ],
+ },
+ {
+ id: 'content',
+ title: i18n.translate('xpack.serverlessSearch.nav.content', {
+ defaultMessage: 'Content',
+ }),
+ children: [
+ {
+ id: 'content_indices',
+ title: i18n.translate('xpack.serverlessSearch.nav.content.indices', {
+ defaultMessage: 'Indices',
+ }),
+ // TODO: this will be updated to a new Indices page
+ href: '/app/management/data/index_management/indices',
+ },
+ {
+ id: 'content_transforms',
+ title: i18n.translate('xpack.serverlessSearch.nav.content.transforms', {
+ defaultMessage: 'Transforms',
+ }),
+ // TODO: this will be updated to a new Transforms page
+ href: '/app/management/ingest/ingest_pipelines',
+ },
+ {
+ id: 'content_indexing_api',
+ title: i18n.translate('xpack.serverlessSearch.nav.content.indexingApi', {
+ defaultMessage: 'Indexing API',
+ }),
+ // TODO: this page does not exist yet, linking to getting started for now
+ href: '/app/elasticsearch',
+ },
+ ],
+ },
+ {
+ id: 'security',
+ title: i18n.translate('xpack.serverlessSearch.nav.security', {
+ defaultMessage: 'Security',
+ }),
+ children: [
+ {
+ id: 'security_api_keys',
+ title: i18n.translate('xpack.serverlessSearch.nav.security.apiKeys', {
+ defaultMessage: 'API Keys',
+ }),
+ href: '/app/management/security/api_keys',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
};
-const navItems: ChromeNavigationNodeViewModel[] = [
- {
- id: 'search_getting_started',
- title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', {
- defaultMessage: 'Getting started',
- }),
- href: '/app/elasticsearch',
- },
- {
- id: 'dev_tools',
- title: i18n.translate('xpack.serverlessSearch.nav.devTools', { defaultMessage: 'Dev Tools' }),
- items: [
- {
- id: 'dev_tools_console',
- title: i18n.translate('xpack.serverlessSearch.nav.devTools.console', {
- defaultMessage: 'Console',
- }),
- href: '/app/dev_tools#/console',
- },
- {
- id: 'dev_tools_profiler',
- title: i18n.translate('xpack.serverlessSearch.nav.devTools.searchProfiler', {
- defaultMessage: 'Search Profiler',
- }),
- href: '/app/dev_tools#/searchprofiler',
- },
- {
- id: 'dev_tools_grok_debugger',
- title: i18n.translate('xpack.serverlessSearch.nav.devTools.grokDebugger', {
- defaultMessage: 'Grok debugger',
- }),
- href: '/app/dev_tools#/grokdebugger',
- },
- {
- id: 'dev_tools_painless_lab',
- title: i18n.translate('xpack.serverlessSearch.nav.devTools.painlessLab', {
- defaultMessage: 'Painless Lab',
- }),
- href: '/app/dev_tools#/painless_lab',
- },
- ],
- },
- {
- id: 'explore',
- title: i18n.translate('xpack.serverlessSearch.nav.explore', { defaultMessage: 'Explore' }),
- items: [
- {
- id: 'explore_discover',
- title: i18n.translate('xpack.serverlessSearch.nav.explore.discover', {
- defaultMessage: 'Discover',
- }),
- href: '/app/discover',
- },
- {
- id: 'explore_dashboard',
- title: i18n.translate('xpack.serverlessSearch.nav.explore.dashboard', {
- defaultMessage: 'Dashboard',
- }),
- href: '/app/dashboards',
- },
- {
- id: 'explore_visualize_library',
- title: i18n.translate('xpack.serverlessSearch.nav.explore.visualizeLibrary', {
- defaultMessage: 'Visualize Library',
- }),
- href: '/app/visualize',
- },
- ],
- },
- {
- id: 'content',
- title: i18n.translate('xpack.serverlessSearch.nav.content', { defaultMessage: 'Content' }),
- items: [
- {
- id: 'content_indices',
- title: i18n.translate('xpack.serverlessSearch.nav.content.indices', {
- defaultMessage: 'Indices',
- }),
- // TODO: this will be updated to a new Indices page
- href: '/app/management/data/index_management/indices',
- },
- {
- id: 'content_transforms',
- title: i18n.translate('xpack.serverlessSearch.nav.content.transforms', {
- defaultMessage: 'Transforms',
- }),
- // TODO: this will be updated to a new Transforms page
- href: '/app/management/ingest/ingest_pipelines',
- },
- {
- id: 'content_indexing_api',
- title: i18n.translate('xpack.serverlessSearch.nav.content.indexingApi', {
- defaultMessage: 'Indexing API',
- }),
- // TODO: this page does not exist yet, linking to getting started for now
- href: '/app/elasticsearch',
- },
- ],
- },
- {
- id: 'security',
- title: i18n.translate('xpack.serverlessSearch.nav.security', { defaultMessage: 'Security' }),
- items: [
- {
- id: 'security_api_keys',
- title: i18n.translate('xpack.serverlessSearch.nav.security.apiKeys', {
- defaultMessage: 'API Keys',
- }),
- href: '/app/management/security/api_keys',
- },
- ],
- },
-];
-
export const createServerlessSearchSideNavComponent =
(core: CoreStart, { serverless }: { serverless: ServerlessPluginStart }) =>
() => {
- // Currently, this allows the "Search" section of the side nav to render as pre-expanded.
- // This will soon be powered from state received from core.chrome
- const activeNavItemId = 'search_project_nav.search_getting_started';
-
return (
-
From 06371502a4cb385b5db53a6e29fde61140382189 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Wed, 31 May 2023 13:04:53 +0100
Subject: [PATCH 15/18] Fix import paths
---
.../navigation/src/ui/components/navigation_header.tsx | 2 +-
.../chrome/navigation/src/ui/default_navigation.test.tsx | 4 ++--
.../chrome/navigation/src/ui/hooks/use_init_navnode.ts | 2 +-
.../chrome/navigation/src/ui/navigation.stories.tsx | 8 ++++----
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_header.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_header.tsx
index e7db1d25aaddb..7895ca62e7a4b 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_header.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_header.tsx
@@ -13,7 +13,7 @@ import { useNavigation as useServices } from '../../services';
import { ElasticMark } from '../elastic_mark';
import { getI18nStrings } from '../i18n_strings';
-import '../../header_logo.scss';
+import '../header_logo.scss';
interface Props {
homeHref: string;
diff --git a/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx
index 0856fb4d2ced4..3820dcd5dd5c9 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/default_navigation.test.tsx
@@ -11,8 +11,8 @@ import { render } from '@testing-library/react';
import { type Observable, of } from 'rxjs';
import type { ChromeNavLink } from '@kbn/core-chrome-browser';
-import { getServicesMock } from '../../../mocks/src/jest';
-import { NavigationProvider } from '../../services';
+import { getServicesMock } from '../../mocks/src/jest';
+import { NavigationProvider } from '../services';
import { DefaultNavigation } from './default_navigation';
import type { ProjectNavigationTreeDefinition, RootNavigationItemDefinition } from './types';
import {
diff --git a/packages/shared-ux/chrome/navigation/src/ui/hooks/use_init_navnode.ts b/packages/shared-ux/chrome/navigation/src/ui/hooks/use_init_navnode.ts
index 2d3e332b7b890..1ee9972f58a12 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/hooks/use_init_navnode.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/hooks/use_init_navnode.ts
@@ -10,7 +10,7 @@ import { ChromeNavLink, ChromeProjectNavigationNode } from '@kbn/core-chrome-bro
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import useObservable from 'react-use/lib/useObservable';
-import { useNavigation as useNavigationServices } from '../../../services';
+import { useNavigation as useNavigationServices } from '../../services';
import {
ChromeProjectNavigationNodeEnhanced,
NodeProps,
diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx
index 987d6d3dda8f9..cc6bd2978a9c5 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx
@@ -24,11 +24,11 @@ import {
EuiTitle,
} from '@elastic/eui';
import { css } from '@emotion/react';
-import { NavigationStorybookMock } from '../../../mocks';
-import mdx from '../../../README.mdx';
-import { NavigationProvider } from '../../services';
+import { NavigationStorybookMock } from '../../mocks';
+import mdx from '../../README.mdx';
+import { NavigationProvider } from '../services';
import { DefaultNavigation } from './default_navigation';
-import type { ChromeNavigationViewModel, NavigationServices } from '../../../types';
+import type { ChromeNavigationViewModel, NavigationServices } from '../../types';
import { Navigation } from './components';
import { ProjectNavigationDefinition } from './types';
import { getPresets } from './nav_tree_presets';
From 376d17c6a36399c1339e54e7f7b0f8d7b1868d94 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Wed, 31 May 2023 13:06:10 +0100
Subject: [PATCH 16/18] Export getPresets handler
---
packages/shared-ux/chrome/navigation/index.ts | 2 +-
.../navigation/src/ui/components/navigation_bucket.tsx | 9 ++-------
packages/shared-ux/chrome/navigation/src/ui/index.ts | 2 ++
.../chrome/navigation/src/ui/nav_tree_presets/index.ts | 5 -----
4 files changed, 5 insertions(+), 13 deletions(-)
diff --git a/packages/shared-ux/chrome/navigation/index.ts b/packages/shared-ux/chrome/navigation/index.ts
index 1d500b72117ec..213147a99a093 100644
--- a/packages/shared-ux/chrome/navigation/index.ts
+++ b/packages/shared-ux/chrome/navigation/index.ts
@@ -8,7 +8,7 @@
export { NavigationKibanaProvider, NavigationProvider } from './src/services';
-export { DefaultNavigation, Navigation } from './src/ui';
+export { DefaultNavigation, Navigation, getPresets } from './src/ui';
export type {
NavigationTreeDefinition,
diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_bucket.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_bucket.tsx
index ac5f9691f6f03..3d287e918d5c8 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_bucket.tsx
+++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_bucket.tsx
@@ -8,16 +8,11 @@
import React, { FC, useCallback } from 'react';
-import { analytics, devtools, ml, management } from '../nav_tree_presets';
+import { getPresets } from '../nav_tree_presets';
import { Navigation } from './navigation';
import type { NavigationGroupPreset, NodeDefinition } from '../types';
-const navTreePresets: { [preset in NavigationGroupPreset]: NodeDefinition } = {
- analytics,
- ml,
- devtools,
- management,
-};
+const navTreePresets = getPresets('all');
export interface Props {
preset?: NavigationGroupPreset;
diff --git a/packages/shared-ux/chrome/navigation/src/ui/index.ts b/packages/shared-ux/chrome/navigation/src/ui/index.ts
index 34089f5ad633b..9091a261b1dd8 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/index.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/index.ts
@@ -10,6 +10,8 @@ export { Navigation } from './components';
export { DefaultNavigation } from './default_navigation';
+export { getPresets } from './nav_tree_presets';
+
export type {
NavigationTreeDefinition,
ProjectNavigationDefinition,
diff --git a/packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/index.ts b/packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/index.ts
index 1cdbdaaa551ea..0aefcf3f92aa7 100644
--- a/packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/index.ts
+++ b/packages/shared-ux/chrome/navigation/src/ui/nav_tree_presets/index.ts
@@ -13,11 +13,6 @@ import { devtools, type ID as DevtoolsID } from './devtools';
import { management, type ID as ManagementID } from './management';
import { ml, type ID as MlID } from './ml';
-export { analytics } from './analytics';
-export { devtools } from './devtools';
-export { ml } from './ml';
-export { management } from './management';
-
export type NodeDefinitionWithChildren = NodeDefinition & {
children: Required>['children'];
};
From 4a91888c73fbb94b6cba307549003292d7c0ed32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Wed, 31 May 2023 13:06:34 +0100
Subject: [PATCH 17/18] Use devtools preset in serverless search nav
---
.../serverless_search/public/layout/nav.tsx | 180 ++++++++----------
1 file changed, 75 insertions(+), 105 deletions(-)
diff --git a/x-pack/plugins/serverless_search/public/layout/nav.tsx b/x-pack/plugins/serverless_search/public/layout/nav.tsx
index cf01f2400b370..a0a6083ccce21 100644
--- a/x-pack/plugins/serverless_search/public/layout/nav.tsx
+++ b/x-pack/plugins/serverless_search/public/layout/nav.tsx
@@ -10,11 +10,14 @@ import {
DefaultNavigation,
NavigationKibanaProvider,
NavigationTreeDefinition,
+ getPresets,
} from '@kbn/shared-ux-chrome-navigation';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { ServerlessPluginStart } from '@kbn/serverless/public';
+const devTools = getPresets('devtools');
+
const navigationTree: NavigationTreeDefinition = {
body: [
{
@@ -26,128 +29,95 @@ const navigationTree: NavigationTreeDefinition = {
id: 'search_project_nav',
title: 'Elasticsearch',
icon: 'logoElasticsearch',
+ defaultIsCollapsed: false,
children: [
{
- id: 'root',
+ id: 'search_getting_started',
+ title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', {
+ defaultMessage: 'Getting started',
+ }),
+ href: '/app/elasticsearch',
+ },
+ {
+ id: 'dev_tools',
+ title: i18n.translate('xpack.serverlessSearch.nav.devTools', {
+ defaultMessage: 'Dev Tools',
+ }),
+ children: devTools.children[0].children,
+ },
+ {
+ id: 'explore',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore', {
+ defaultMessage: 'Explore',
+ }),
children: [
{
- id: 'search_getting_started',
- title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', {
- defaultMessage: 'Getting started',
+ id: 'explore_discover',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore.discover', {
+ defaultMessage: 'Discover',
}),
- href: '/app/elasticsearch',
+ href: '/app/discover',
},
{
- id: 'dev_tools',
- title: i18n.translate('xpack.serverlessSearch.nav.devTools', {
- defaultMessage: 'Dev Tools',
+ id: 'explore_dashboard',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore.dashboard', {
+ defaultMessage: 'Dashboard',
}),
- children: [
- {
- id: 'dev_tools_console',
- title: i18n.translate('xpack.serverlessSearch.nav.devTools.console', {
- defaultMessage: 'Console',
- }),
- href: '/app/dev_tools#/console',
- },
- {
- id: 'dev_tools_profiler',
- title: i18n.translate('xpack.serverlessSearch.nav.devTools.searchProfiler', {
- defaultMessage: 'Search Profiler',
- }),
- href: '/app/dev_tools#/searchprofiler',
- },
- {
- id: 'dev_tools_grok_debugger',
- title: i18n.translate('xpack.serverlessSearch.nav.devTools.grokDebugger', {
- defaultMessage: 'Grok debugger',
- }),
- href: '/app/dev_tools#/grokdebugger',
- },
- {
- id: 'dev_tools_painless_lab',
- title: i18n.translate('xpack.serverlessSearch.nav.devTools.painlessLab', {
- defaultMessage: 'Painless Lab',
- }),
- href: '/app/dev_tools#/painless_lab',
- },
- ],
+ href: '/app/dashboards',
},
{
- id: 'explore',
- title: i18n.translate('xpack.serverlessSearch.nav.explore', {
- defaultMessage: 'Explore',
+ id: 'explore_visualize_library',
+ title: i18n.translate('xpack.serverlessSearch.nav.explore.visualizeLibrary', {
+ defaultMessage: 'Visualize Library',
}),
- children: [
- {
- id: 'explore_discover',
- title: i18n.translate('xpack.serverlessSearch.nav.explore.discover', {
- defaultMessage: 'Discover',
- }),
- href: '/app/discover',
- },
- {
- id: 'explore_dashboard',
- title: i18n.translate('xpack.serverlessSearch.nav.explore.dashboard', {
- defaultMessage: 'Dashboard',
- }),
- href: '/app/dashboards',
- },
- {
- id: 'explore_visualize_library',
- title: i18n.translate('xpack.serverlessSearch.nav.explore.visualizeLibrary', {
- defaultMessage: 'Visualize Library',
- }),
- href: '/app/visualize',
- },
- ],
+ href: '/app/visualize',
},
+ ],
+ },
+ {
+ id: 'content',
+ title: i18n.translate('xpack.serverlessSearch.nav.content', {
+ defaultMessage: 'Content',
+ }),
+ children: [
{
- id: 'content',
- title: i18n.translate('xpack.serverlessSearch.nav.content', {
- defaultMessage: 'Content',
+ id: 'content_indices',
+ title: i18n.translate('xpack.serverlessSearch.nav.content.indices', {
+ defaultMessage: 'Indices',
}),
- children: [
- {
- id: 'content_indices',
- title: i18n.translate('xpack.serverlessSearch.nav.content.indices', {
- defaultMessage: 'Indices',
- }),
- // TODO: this will be updated to a new Indices page
- href: '/app/management/data/index_management/indices',
- },
- {
- id: 'content_transforms',
- title: i18n.translate('xpack.serverlessSearch.nav.content.transforms', {
- defaultMessage: 'Transforms',
- }),
- // TODO: this will be updated to a new Transforms page
- href: '/app/management/ingest/ingest_pipelines',
- },
- {
- id: 'content_indexing_api',
- title: i18n.translate('xpack.serverlessSearch.nav.content.indexingApi', {
- defaultMessage: 'Indexing API',
- }),
- // TODO: this page does not exist yet, linking to getting started for now
- href: '/app/elasticsearch',
- },
- ],
+ // TODO: this will be updated to a new Indices page
+ href: '/app/management/data/index_management/indices',
},
{
- id: 'security',
- title: i18n.translate('xpack.serverlessSearch.nav.security', {
- defaultMessage: 'Security',
+ id: 'content_transforms',
+ title: i18n.translate('xpack.serverlessSearch.nav.content.transforms', {
+ defaultMessage: 'Transforms',
+ }),
+ // TODO: this will be updated to a new Transforms page
+ href: '/app/management/ingest/ingest_pipelines',
+ },
+ {
+ id: 'content_indexing_api',
+ title: i18n.translate('xpack.serverlessSearch.nav.content.indexingApi', {
+ defaultMessage: 'Indexing API',
+ }),
+ // TODO: this page does not exist yet, linking to getting started for now
+ href: '/app/elasticsearch',
+ },
+ ],
+ },
+ {
+ id: 'security',
+ title: i18n.translate('xpack.serverlessSearch.nav.security', {
+ defaultMessage: 'Security',
+ }),
+ children: [
+ {
+ id: 'security_api_keys',
+ title: i18n.translate('xpack.serverlessSearch.nav.security.apiKeys', {
+ defaultMessage: 'API Keys',
}),
- children: [
- {
- id: 'security_api_keys',
- title: i18n.translate('xpack.serverlessSearch.nav.security.apiKeys', {
- defaultMessage: 'API Keys',
- }),
- href: '/app/management/security/api_keys',
- },
- ],
+ href: '/app/management/security/api_keys',
},
],
},
From 558622c4178f09f8b2087e4f63c3e3b98503d17a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Wed, 31 May 2023 13:39:54 +0100
Subject: [PATCH 18/18] Update observability side nav
---
.../components/side_navigation/index.tsx | 206 ++++++++++--------
1 file changed, 112 insertions(+), 94 deletions(-)
diff --git a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx
index f4393eee4417e..e318e5301f262 100644
--- a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx
+++ b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx
@@ -8,110 +8,128 @@
import { CoreStart } from '@kbn/core/public';
import { ServerlessPluginStart } from '@kbn/serverless/public';
import {
- ChromeNavigationNodeViewModel,
- Navigation,
+ DefaultNavigation,
NavigationKibanaProvider,
+ NavigationTreeDefinition,
+ getPresets,
} from '@kbn/shared-ux-chrome-navigation';
import React from 'react';
-// #TODO translate titles?
-const navItems: ChromeNavigationNodeViewModel[] = [
- {
- id: 'services-infra',
- items: [
- { id: 'services', title: 'Services', href: '/app/apm/services' },
- {
- id: 'infra',
- title: 'Infrastructure',
- href: '/app/metrics/inventory',
- },
- ],
- },
- {
- id: 'alerts-cases-slos',
- items: [
- {
- id: 'alerts',
- title: 'Alerts',
- href: '/app/observability/alerts',
- },
- {
- id: 'Cases',
- title: 'Cases',
- href: '/app/observability/cases',
- },
- {
- id: 'slos',
- title: 'SLOs',
- href: '/app/observability/slos',
- },
- ],
- },
- {
- id: 'signals',
- title: 'Signals',
- items: [
- {
- id: 'traces',
- title: 'Traces',
- href: '/app/apm/traces',
- },
- {
- id: 'logs',
- title: 'Logs',
- href: '/app/logs/stream',
- },
- ],
- },
- {
- id: 'toolbox',
- title: 'Toolbox',
- items: [
- {
- id: 'visualization',
- title: 'Visualization',
- href: '/app/visualize',
- },
- {
- id: 'dashboards',
- title: 'Dashboards',
- href: '/app/dashboards',
- },
- ],
- },
- {
- id: 'on-boarding',
- items: [
- {
- id: 'get-started',
- title: 'Get started',
- icon: 'launch',
- href: '/app/observabilityOnboarding',
- },
- ],
- },
-];
+const navigationTree: NavigationTreeDefinition = {
+ body: [
+ {
+ type: 'cloudLink',
+ preset: 'projects',
+ },
+ {
+ type: 'navGroup',
+ id: 'observability_project_nav',
+ title: 'Observability',
+ icon: 'logoObservability',
+ defaultIsCollapsed: false,
+ children: [
+ {
+ id: 'services-infra',
+ children: [
+ { id: 'services', title: 'Services', href: '/app/apm/services' },
+ {
+ id: 'infra',
+ title: 'Infrastructure',
+ href: '/app/metrics/inventory',
+ },
+ ],
+ },
+ {
+ id: 'alerts-cases-slos',
+ children: [
+ {
+ id: 'alerts',
+ title: 'Alerts',
+ href: '/app/observability/alerts',
+ },
+ {
+ id: 'Cases',
+ title: 'Cases',
+ href: '/app/observability/cases',
+ },
+ {
+ id: 'slos',
+ title: 'SLOs',
+ href: '/app/observability/slos',
+ },
+ ],
+ },
+ {
+ id: 'signals',
+ title: 'Signals',
+ children: [
+ {
+ id: 'traces',
+ title: 'Traces',
+ href: '/app/apm/traces',
+ },
+ {
+ id: 'logs',
+ title: 'Logs',
+ href: '/app/logs/stream',
+ },
+ ],
+ },
+ {
+ id: 'toolbox',
+ title: 'Toolbox',
+ children: [
+ {
+ id: 'visualization',
+ title: 'Visualization',
+ href: '/app/visualize',
+ },
+ {
+ id: 'dashboards',
+ title: 'Dashboards',
+ href: '/app/dashboards',
+ },
+ ],
+ },
+ {
+ id: 'on-boarding',
+ children: [
+ {
+ id: 'get-started',
+ title: 'Get started',
+ icon: 'launch',
+ href: '/app/observabilityOnboarding',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'navGroup',
+ ...getPresets('analytics'),
+ },
+ {
+ type: 'navGroup',
+ ...getPresets('ml'),
+ },
+ ],
+ footer: [
+ {
+ type: 'navGroup',
+ ...getPresets('management'),
+ },
+ ],
+};
export const getObservabilitySideNavComponent =
(core: CoreStart, { serverless }: { serverless: ServerlessPluginStart }) =>
() => {
- const activeNavItemId = 'observability_project_nav.root';
-
return (
-
);