Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -27,6 +27,7 @@ import { ApmIndicatorOverview } from '../overview/apm_indicator_overview';
import { DisplayQuery } from '../overview/display_query';
import { DefinitionItem } from './definition_item';
import { SyntheticsIndicatorOverview } from '../overview/synthetics_indicator_overview';
import { LinkedDashboards } from './linked_dashboards';

export interface Props {
slo: SLOWithSummaryResponse;
Expand Down Expand Up @@ -153,6 +154,12 @@ export function Definition({ slo }: Props) {
})}
subtitle={slo.settings.frequency}
/>
<DefinitionItem
title={i18n.translate('xpack.slo.sloDetails.overview.dashboards', {
defaultMessage: 'Linked dashboards',
})}
subtitle={<LinkedDashboards dashboards={slo.artifacts?.dashboards ?? []} />}
/>
</EuiFlexGrid>
</EuiPanel>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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 { EuiFlexGroup, EuiFlexItem, EuiLink, EuiLoadingSpinner, EuiText } from '@elastic/eui';
import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
import React, { useMemo } from 'react';
import type { DashboardLocatorParams } from '@kbn/dashboard-plugin/common';
import { useQuery } from '@tanstack/react-query';
import { i18n } from '@kbn/i18n';
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
import type { SLODefinition } from '../../../../../server/domain/models';
import { useKibana } from '../../../../hooks/use_kibana';

interface Props {
dashboards: NonNullable<NonNullable<SLODefinition['artifacts']>['dashboards']>;
}

interface LinkedDashboard {
id: string;
title: string;
}

const getDashboards = async (
dashboardsIds: string[],
dashboardStart: DashboardStart
): Promise<LinkedDashboard[]> => {
if (dashboardsIds.length === 0) {
return [];
}

const findDashboardsService = await dashboardStart.findDashboardsService();

const dashboards = await findDashboardsService.findByIds(dashboardsIds);

return dashboards.flatMap((dashboard) =>
dashboard.status === 'success' ? [{ id: dashboard.id, title: dashboard.attributes.title }] : []
);
};

export function LinkedDashboards({ dashboards }: Props) {
const {
services: { share },
services: { dashboard: dashboardStart },
} = useKibana();

const dashboardLocator = share.url.locators.get<DashboardLocatorParams>(DASHBOARD_APP_LOCATOR);

const dashboardsIds = useMemo(() => dashboards.map((dashboard) => dashboard.id), [dashboards]);

const { data, isLoading } = useQuery({
queryKey: ['SLO-dashboards', ...dashboardsIds],
queryFn: () => getDashboards(dashboardsIds, dashboardStart),
refetchOnWindowFocus: false,
});

if (isLoading) {
return <EuiLoadingSpinner size="m" />;
}

if (!data || data.length === 0) {
return (
<EuiText size="s">
{i18n.translate('xpack.slo.sloDetails.overview.noDashboards', {
defaultMessage: 'No linked dashboards',
})}
</EuiText>
);
}

return (
<EuiFlexGroup direction="column" gutterSize="xs">
{data.map((dashboardAsset) => {
return (
<EuiFlexItem grow={false} key={dashboardAsset.id}>
<EuiLink
data-test-subj="dashboardAssetLink"
href={dashboardLocator?.getRedirectUrl({ dashboardId: dashboardAsset.id })}
target="_blank"
>
{dashboardAsset.title}
</EuiLink>
</EuiFlexItem>
);
})}
</EuiFlexGroup>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { DashboardsSelector } from '@kbn/dashboards-selector';
Copy link
Contributor

Choose a reason for hiding this comment

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

Great for creating this reusable component!

import { useKibana } from '../../../hooks/use_kibana';
import { useFetchSLOSuggestions } from '../hooks/use_fetch_suggestions';
import type { CreateSLOForm } from '../types';
import { OptionalText } from './common/optional_text';
Expand All @@ -29,6 +31,7 @@ export function SloEditFormDescriptionSection() {
const tagsId = useGeneratedHtmlId({ prefix: 'tags' });

const { suggestions } = useFetchSLOSuggestions();
const { services } = useKibana();

return (
<EuiPanel
Expand Down Expand Up @@ -98,6 +101,7 @@ export function SloEditFormDescriptionSection() {
label={i18n.translate('xpack.slo.sloEdit.tags.label', {
defaultMessage: 'Tags',
})}
labelAppend={<OptionalText />}
>
<Controller
name="tags"
Expand Down Expand Up @@ -146,6 +150,27 @@ export function SloEditFormDescriptionSection() {
)}
/>
</EuiFormRow>
<EuiFormRow
fullWidth
label={i18n.translate('xpack.slo.sloEdit.dashboards.label', {
defaultMessage: 'Linked dashboards',
})}
labelAppend={<OptionalText />}
>
<Controller
name="artifacts.dashboards"
control={control}
defaultValue={undefined}
render={({ field }) => (
<DashboardsSelector
contentManagement={services.contentManagement}
dashboardsFormData={field.value ?? []}
placeholder={DASHBOARDS_COMBOBOX_PLACEHOLDER}
onChange={(selected) => field.onChange(selected.map((d) => ({ id: d.value })))}
/>
)}
/>
</EuiFormRow>
</EuiPanel>
);
}
Expand All @@ -157,3 +182,7 @@ function generateTagOptions(tags: string[] = []) {
'data-test-subj': `${tag}Option`,
}));
}

const DASHBOARDS_COMBOBOX_PLACEHOLDER = i18n.translate('xpack.slo.sloEdit.dashboards.placeholder', {
defaultMessage: 'Add dashboards',
});
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ export function transformSloResponseToCreateSloForm(
: SETTINGS_DEFAULT_VALUES.frequency,
syncField: values.settings?.syncField ?? null,
},
artifacts: {
dashboards: values.artifacts?.dashboards || [],
},
};
}

Expand Down Expand Up @@ -93,6 +96,9 @@ export function transformCreateSLOFormToCreateSLOInput(values: CreateSLOForm): C
frequency: `${values.settings.frequency ?? SETTINGS_DEFAULT_VALUES.frequency}m`,
syncField: values.settings.syncField,
},
artifacts: {
dashboards: values.artifacts?.dashboards || [],
},
};
}

Expand Down Expand Up @@ -125,6 +131,9 @@ export function transformValuesToUpdateSLOInput(values: CreateSLOForm): UpdateSL
frequency: `${values.settings.frequency ?? SETTINGS_DEFAULT_VALUES.frequency}m`,
syncField: values.settings.syncField,
},
artifacts: {
dashboards: values.artifacts?.dashboards || [],
},
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ export interface CreateSLOForm<IndicatorType = Indicator> {
frequency: number; // in minutes
syncField: string | null;
};
artifacts?: {
dashboards?: { id: string }[];
};
}
2 changes: 2 additions & 0 deletions x-pack/solutions/observability/plugins/slo/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import type {
import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin-types-public';
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
import type { ApmSourceAccessPluginStart } from '@kbn/apm-sources-access-plugin/public';
import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
import type { SLORouteRepository } from '../server/routes/get_slo_server_route_repository';
import type { SLOPlugin } from './plugin';

Expand Down Expand Up @@ -106,6 +107,7 @@ export interface SLOPublicPluginsStart {
security?: SecurityPluginStart;
fieldsMetadata: FieldsMetadataPublicStart;
apmSourcesAccess: ApmSourceAccessPluginStart;
contentManagement: ContentManagementPublicStart;
Copy link
Contributor

Choose a reason for hiding this comment

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

Linking with this issue, where we can remove the dependency to the contentManagement plugin

Copy link
Contributor

Choose a reason for hiding this comment

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

Just to clarify, this comment of mine regarding removing dependency to the contentManagement plugin, is something that does NOT need to be addressed in current PR. I put it here as a reminder for when addressing this issue.

I also just read Dominique's here, so it might be that we don't need to replace anything (I need to refresh what it is about). @cesco-f Can you weigh in here if we can replace the use of dashboardServiceProvider with the api in the rules form?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I left a comment there!

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, we discussed about it! With the new abstract dashboards selector component you introduce in this PR, there is no need to use the linked dashboards api in the rule form.

}

export type SLOPublicSetup = ReturnType<SLOPlugin['setup']>;
Expand Down
3 changes: 3 additions & 0 deletions x-pack/solutions/observability/plugins/slo/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,8 @@
"@kbn/lock-manager",
"@kbn/object-utils",
"@kbn/licensing-types",
"@kbn/deeplinks-analytics",
"@kbn/content-management-plugin",
"@kbn/dashboards-selector",
]
}