Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
51cccc7
Add empty dashboard tab based on the frature flag
jennypavlova Mar 6, 2024
00f0202
Update saved object to support hostNameFilterEnabled flag for every d…
jennypavlova Mar 7, 2024
e642273
Add hook to get custom dashboards per asset type
jennypavlova Mar 7, 2024
c5fface
[WIP] Link dashboards
jennypavlova Mar 8, 2024
ec54d3d
Add dashboard id to the url state
jennypavlova Mar 11, 2024
18e3b14
Add unlink dashboard action
jennypavlova Mar 11, 2024
bf14ccc
Small ids and translations fixes and cleanup
jennypavlova Mar 11, 2024
bda99c1
Tex changes and cleanup
jennypavlova Mar 12, 2024
f132a13
Add Dashboards Tab splash screen test
jennypavlova Mar 12, 2024
dcb9241
Change unlink
jennypavlova Mar 12, 2024
57ef8dc
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Mar 12, 2024
0663734
Change empty state content
jennypavlova Mar 12, 2024
a424746
Merge branch 'main' of github.com:elastic/kibana into 176070-infra-im…
jennypavlova Mar 12, 2024
81c0639
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Mar 12, 2024
afe35d7
Config fix
jennypavlova Mar 12, 2024
6b71389
Merge branch '176070-infra-implement-dashboard-tab-ui-in-host-details…
jennypavlova Mar 12, 2024
79d095c
Fix translation
jennypavlova Mar 12, 2024
f0b7289
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Mar 12, 2024
4e4b8e6
[CI] Auto-commit changed files from 'node scripts/check_mappings_upda…
kibanamachine Mar 12, 2024
5bc706f
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Mar 12, 2024
d95ddb6
Tests fixes
jennypavlova Mar 13, 2024
8d67a50
Merge branch '176070-infra-implement-dashboard-tab-ui-in-host-details…
jennypavlova Mar 13, 2024
cf12a2e
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Mar 13, 2024
abea5bf
Set default dashboard selection and add loading check
jennypavlova Mar 13, 2024
c6384c4
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Mar 13, 2024
d448899
CR changes
jennypavlova Mar 14, 2024
ee852ef
CR changes
jennypavlova Mar 18, 2024
4d637e6
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Mar 18, 2024
ed558a0
fix translation key
jennypavlova Mar 18, 2024
415bbbc
getFilterByAssetName CR changes
jennypavlova Mar 19, 2024
501b46f
Rename to GotoDashboardLink
jennypavlova Mar 19, 2024
dfc96db
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Mar 21, 2024
6e05055
Update custom dashboards saved object
jennypavlova Mar 21, 2024
f5aa13d
[CI] Auto-commit changed files from 'node scripts/check_mappings_upda…
kibanamachine Mar 21, 2024
6689a06
[CI] Auto-commit changed files from 'node scripts/jest_integration -u…
kibanamachine Mar 21, 2024
74d5d3e
Merge branch 'main' of github.com:elastic/kibana into 176070-infra-im…
jennypavlova Apr 5, 2024
dfa6b91
Rename to useFetchCustomDashboards
jennypavlova Apr 5, 2024
e7ad2f1
Fix switching dashboards not refreshing the content issue
jennypavlova Apr 5, 2024
09c756a
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Apr 5, 2024
7d63c93
CR changes
jennypavlova Apr 8, 2024
7076b6e
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Apr 8, 2024
a398a19
Remove unused imports
jennypavlova Apr 8, 2024
f4f317e
CR changes
jennypavlova Apr 8, 2024
758e2c2
Fix import
jennypavlova Apr 9, 2024
2d097b8
Use callback for clicks and fix add/edit message
jennypavlova Apr 9, 2024
cd414fc
Fix notifications text param
jennypavlova Apr 9, 2024
aab661c
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Apr 9, 2024
d2117c8
CR changes
jennypavlova Apr 9, 2024
0eebd0e
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Apr 9, 2024
b651ac2
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Apr 9, 2024
7538522
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Apr 10, 2024
93aae78
Merge branch 'main' into 176070-infra-implement-dashboard-tab-ui-in-h…
jennypavlova Apr 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ function getEditSuccessToastLabels(dashboardName: string) {
}
),
text: i18n.translate('xpack.apm.serviceDashboards.editSuccess.toast.text', {
defaultMessage: 'Your dashboard link have been updated',
defaultMessage: 'Your dashboard link has been updated',
}),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ export interface InfraCustomDashboard {
export interface InfraSavedCustomDashboard extends InfraCustomDashboard {
id: string;
}

export interface DashboardItemWithTitle extends InfraSavedCustomDashboard {
title: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const SavedObjectIdRT = rt.type({
id: rt.string,
});

const InfraCustomDashboardRT = rt.intersection([AssetTypeRT, PayloadRT, SavedObjectIdRT]);
export const InfraCustomDashboardRT = rt.intersection([AssetTypeRT, PayloadRT, SavedObjectIdRT]);

/**
GET endpoint
Expand Down Expand Up @@ -59,3 +59,4 @@ export const InfraDeleteCustomDashboardsRequestParamsRT = rt.intersection([
AssetTypeRT,
SavedObjectIdRT,
]);
export const InfraDeleteCustomDashboardsResponseBodyRT = rt.string;
1 change: 1 addition & 0 deletions x-pack/plugins/observability_solution/infra/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"data",
"dataViews",
"dataViewEditor",
"dashboard",
"discover",
"embeddable",
"features",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,10 @@ export const commonFlyoutTabs: Tab[] = [
defaultMessage: 'Osquery',
}),
},
{
id: ContentTabIds.DASHBOARDS,
name: i18n.translate('xpack.infra.infra.nodeDetails.tabs.dashboards', {
defaultMessage: 'Dashboards',
}),
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React from 'react';
import { DatePicker } from '../date_picker/date_picker';
import { useTabSwitcherContext } from '../hooks/use_tab_switcher';
import { Anomalies, Logs, Metadata, Osquery, Overview, Processes, Profiling } from '../tabs';
import {
Anomalies,
Dashboards,
Logs,
Metadata,
Osquery,
Overview,
Processes,
Profiling,
} from '../tabs';
import { ContentTabIds } from '../types';

export const Content = () => {
Expand All @@ -23,6 +32,7 @@ export const Content = () => {
ContentTabIds.METADATA,
ContentTabIds.PROCESSES,
ContentTabIds.ANOMALIES,
ContentTabIds.DASHBOARDS,
]}
/>
</EuiFlexItem>
Expand All @@ -48,6 +58,9 @@ export const Content = () => {
<TabPanel activeWhen={ContentTabIds.PROFILING}>
<Profiling />
</TabPanel>
<TabPanel activeWhen={ContentTabIds.DASHBOARDS}>
<Dashboards />
</TabPanel>
</EuiFlexItem>
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const TabIdRT = rt.union([
rt.literal(ContentTabIds.LOGS),
rt.literal(ContentTabIds.ANOMALIES),
rt.literal(ContentTabIds.OSQUERY),
rt.literal(ContentTabIds.DASHBOARDS),
]);

const AlertStatusRT = rt.union([
Expand All @@ -84,6 +85,7 @@ const AssetDetailsUrlStateRT = rt.partial({
logsSearch: rt.string,
profilingSearch: rt.string,
alertStatus: AlertStatusRT,
dashboardId: rt.string,
});

const AssetDetailsUrlRT = rt.union([AssetDetailsUrlStateRT, rt.null]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of boilerplate is needed to call the useTrackedPromise. It'd be nice if we had something like the callApmApi utils, eg:

await callApmApi('POST /internal/apm/custom-dashboard', {
            params: {
              query: { customDashboardId: currentDashboard?.id },
              body: {
                dashboardSavedObjectId: newDashboard.value,
                serviceEnvironmentFilterEnabled: serviceFiltersEnabled,
                serviceNameFilterEnabled: serviceFiltersEnabled,
                kuery: `${SERVICE_NAME}: ${serviceName}`,
              },
            },
            signal: null,
          })

I'm not sure why we need the abstraction contained in useTrackedPromise in infra in general.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it will be nice to have for sure! I searched for something similar but I couldn't find anything in Infra. We can think about adding something like that in the future. I found only the useTrackedPromise used in a similar way and I agree about the boilerplate but I am not sure how to improve it (I tried to reuse some functions where possible (for error handling and response decoding).
Another similar case for calling an endpoint on button click is in inventory saved views but there we have the InventoryViewsClient and there is also boilerplate around the solution (also the usage of react query (example here) can help in this case as well but I tried to not overcomplicate it and keep it similar to the APM solution)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right. let's not overcomplicate this PR, but I guess we can open a ticket to create a solution similar to APM's?

* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useCallback } from 'react';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import { i18n } from '@kbn/i18n';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import type {
InfraCustomDashboard,
InfraSavedCustomDashboard,
InfraCustomDashboardAssetType,
} from '../../../../common/custom_dashboards';
import {
InfraCustomDashboardRT,
InfraDeleteCustomDashboardsResponseBodyRT,
} from '../../../../common/http_api/custom_dashboards_api';
import { throwErrors, createPlainError } from '../../../../common/runtime_types';

type ActionType = 'create' | 'update' | 'delete';
const errorMessages: Record<ActionType, string> = {
create: i18n.translate('xpack.infra.linkDashboards.addFailure.toast.title', {
defaultMessage: 'Error while linking dashboards',
}),
update: i18n.translate('xpack.infra.updatingLinkedDashboards.addFailure.toast.title', {
defaultMessage: 'Error while updating linked dashboards',
}),
delete: i18n.translate('xpack.infra.deletingLinkedDashboards.addFailure.toast.title', {
defaultMessage: 'Error while deleting linked dashboards',
}),
};

const decodeResponse = (response: any) => {
return pipe(
InfraCustomDashboardRT.decode(response),
fold(throwErrors(createPlainError), identity)
);
};

export const useUpdateCustomDashboard = () => {
const { services } = useKibanaContextForPlugin();
const { notifications } = useKibana();

const onError = useCallback(
(errorMessage: string) => {
if (errorMessage) {
notifications.toasts.danger({
title: errorMessages.update,
body: errorMessage,
});
}
},
[notifications.toasts]
);

const [updateCustomDashboardRequest, updateCustomDashboard] = useTrackedPromise(
{
cancelPreviousOn: 'resolution',
createPromise: async ({
assetType,
id,
dashboardSavedObjectId,
dashboardFilterAssetIdEnabled,
}: InfraSavedCustomDashboard) => {
const rawResponse = await services.http.fetch(
`/api/infra/${assetType}/custom-dashboards/${id}`,
{
method: 'PUT',
body: JSON.stringify({
assetType,
dashboardSavedObjectId,
dashboardFilterAssetIdEnabled,
}),
}
);

return decodeResponse(rawResponse);
},
onResolve: (response) => response,
onReject: (e: Error | unknown) => onError((e as Error)?.message),
},
[]
);

const isUpdateLoading = updateCustomDashboardRequest.state === 'pending';

const hasUpdateError = updateCustomDashboardRequest.state === 'rejected';

return {
updateCustomDashboard,
isUpdateLoading,
hasUpdateError,
};
};

export const useDeleteCustomDashboard = () => {
const { services } = useKibanaContextForPlugin();
const { notifications } = useKibana();

const decodeDeleteResponse = (response: any) => {
return pipe(
InfraDeleteCustomDashboardsResponseBodyRT.decode(response),
fold(throwErrors(createPlainError), identity)
);
};

const onError = useCallback(
(errorMessage: string) => {
if (errorMessage) {
notifications.toasts.danger({
title: errorMessages.delete,
body: errorMessage,
});
}
},
[notifications.toasts]
);

const [deleteCustomDashboardRequest, deleteCustomDashboard] = useTrackedPromise(
{
cancelPreviousOn: 'resolution',
createPromise: async ({
assetType,
id,
}: {
assetType: InfraCustomDashboardAssetType;
id: string;
}) => {
const rawResponse = await services.http.fetch(
`/api/infra/${assetType}/custom-dashboards/${id}`,
{
method: 'DELETE',
}
);

return decodeDeleteResponse(rawResponse);
},
onResolve: (response) => response,
onReject: (e: Error | unknown) => onError((e as Error)?.message),
},
[]
);

const isDeleteLoading = deleteCustomDashboardRequest.state === 'pending';

const hasDeleteError = deleteCustomDashboardRequest.state === 'rejected';

return {
deleteCustomDashboard,
isDeleteLoading,
hasDeleteError,
};
};

export const useCreateCustomDashboard = () => {
const { services } = useKibanaContextForPlugin();
const { notifications } = useKibana();

const onError = useCallback(
(errorMessage: string) => {
if (errorMessage) {
notifications.toasts.danger({
title: errorMessages.delete,
body: errorMessage,
});
}
},
[notifications.toasts]
);

const [createCustomDashboardRequest, createCustomDashboard] = useTrackedPromise(
{
cancelPreviousOn: 'resolution',
createPromise: async ({
assetType,
dashboardSavedObjectId,
dashboardFilterAssetIdEnabled,
}: InfraCustomDashboard) => {
const rawResponse = await services.http.fetch(`/api/infra/${assetType}/custom-dashboards`, {
method: 'POST',
body: JSON.stringify({
dashboardSavedObjectId,
dashboardFilterAssetIdEnabled,
}),
});

return decodeResponse(rawResponse);
},
onResolve: (response) => response,
onReject: (e: Error | unknown) => onError((e as Error)?.message),
},
[]
);

const isCreateLoading = createCustomDashboardRequest.state === 'pending';

const hasCreateError = createCustomDashboardRequest.state === 'rejected';

return {
createCustomDashboard,
isCreateLoading,
hasCreateError,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useState, useEffect } from 'react';
import type { SearchDashboardsResponse } from '@kbn/dashboard-plugin/public/services/dashboard_content_management/lib/find_dashboards';
import { i18n } from '@kbn/i18n';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';

export enum FETCH_STATUS {
LOADING = 'loading',
SUCCESS = 'success',
FAILURE = 'failure',
NOT_INITIATED = 'not_initiated',
}

export interface SearchDashboardsResult {
data: SearchDashboardsResponse['hits'];
status: FETCH_STATUS;
}

export function useDashboardFetcher(query = ''): SearchDashboardsResult {
const {
services: { dashboard },
} = useKibanaContextForPlugin();
const { notifications } = useKibana();
const [result, setResult] = useState<SearchDashboardsResult>({
data: [],
status: FETCH_STATUS.NOT_INITIATED,
});

useEffect(() => {
const getDashboards = async () => {
setResult({
data: [],
status: FETCH_STATUS.LOADING,
});
try {
const findDashboardsService = await dashboard?.findDashboardsService();
const data = await findDashboardsService.search({
search: query,
size: 1000,
});

setResult({
data: data.hits,
status: FETCH_STATUS.SUCCESS,
});
} catch (error) {
setResult({
data: [],
status: FETCH_STATUS.FAILURE,
});
Comment on lines +54 to +57

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should call the toast notification to let the user know that an error happened, and pass the exception to it?

notifications.toasts.danger({
title: i18n.translate('xpack.infra.fetchingDashboards.addFailure.toast.title', {
defaultMessage: 'Error while fetching dashboards',
}),
body: error.message,
});
}
};
getDashboards();
}, [dashboard, notifications.toasts, query]);
return result;
}
Loading