in x-pack/solutions/observability/plugins/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx [60:477]
export function SloListCompactView({ sloList, loading, error }: Props) {
const { services } = useKibana();
const {
application: { navigateToUrl },
http: { basePath },
uiSettings,
share: {
url: { locators },
},
triggersActionsUi: { ruleTypeRegistry, actionTypeRegistry },
} = services;
const spaceId = useSpace();
const percentFormat = uiSettings.get('format:percent:defaultPattern');
const sloIdsAndInstanceIds = sloList.map((slo) => [slo.id, slo.instanceId] as [string, string]);
const { data: permissions } = usePermissions();
const filteredRuleTypes = useGetFilteredRuleTypes();
const queryClient = useQueryClient();
const { triggerAction } = useActionModal();
const [sloToAddRule, setSloToAddRule] = useState<SLOWithSummaryResponse | undefined>(undefined);
const handleSavedRule = () => {
queryClient.invalidateQueries({ queryKey: sloKeys.rules(), exact: false });
setSloToAddRule(undefined);
};
const { data: activeAlertsBySlo } = useFetchActiveAlerts({ sloIdsAndInstanceIds });
const { data: rulesBySlo } = useFetchRulesForSlo({
sloIds: sloIdsAndInstanceIds.map((item) => item[0]),
});
const { isLoading: historicalSummaryLoading, data: historicalSummaries = [] } =
useFetchHistoricalSummary({
sloList,
});
const isRemote = (slo: SLOWithSummaryResponse) => !!slo.remote;
const hasRemoteKibanaUrl = (slo: SLOWithSummaryResponse) =>
!!slo.remote && slo.remote.kibanaUrl !== '';
const buildActionName = (actionName: string) => (slo: SLOWithSummaryResponse) =>
isRemote(slo) ? (
<>
{actionName}
<EuiIcon
type="popout"
size="s"
css={{
marginLeft: '10px',
}}
/>
</>
) : (
actionName
);
const actions: Array<DefaultItemAction<SLOWithSummaryResponse>> = [
{
type: 'icon',
icon: 'inspect',
name: i18n.translate('xpack.slo.item.actions.details', {
defaultMessage: 'Details',
}),
description: i18n.translate('xpack.slo.item.actions.details', {
defaultMessage: 'Details',
}),
onClick: (slo: SLOWithSummaryResponse) => {
const sloDetailsUrl = basePath.prepend(
paths.sloDetails(slo.id, slo.instanceId, slo.remote?.remoteName)
);
navigateToUrl(sloDetailsUrl);
},
},
{
type: 'icon',
icon: 'pencil',
name: buildActionName(
i18n.translate('xpack.slo.item.actions.edit', {
defaultMessage: 'Edit',
})
),
description: i18n.translate('xpack.slo.item.actions.edit', {
defaultMessage: 'Edit',
}),
'data-test-subj': 'sloActionsEdit',
enabled: (slo) =>
(permissions?.hasAllWriteRequested && !isRemote(slo)) || hasRemoteKibanaUrl(slo),
onClick: (slo: SLOWithSummaryResponse) => {
const remoteEditUrl = createRemoteSloEditUrl(slo, spaceId);
if (!!remoteEditUrl) {
window.open(remoteEditUrl, '_blank');
} else {
navigateToUrl(basePath.prepend(paths.sloEdit(slo.id)));
}
},
},
{
type: 'icon',
icon: 'bell',
name: i18n.translate('xpack.slo.item.actions.createRule', {
defaultMessage: 'Create new alert rule',
}),
description: i18n.translate('xpack.slo.item.actions.createRule', {
defaultMessage: 'Create new alert rule',
}),
'data-test-subj': 'sloActionsCreateRule',
enabled: (slo: SLOWithSummaryResponse) =>
!!permissions?.hasAllWriteRequested && !isRemote(slo),
onClick: (slo: SLOWithSummaryResponse) => {
setSloToAddRule(slo);
},
},
{
type: 'icon',
icon: 'gear',
name: i18n.translate('xpack.slo.item.actions.manageRules', {
defaultMessage: 'Manage rules',
}),
description: i18n.translate('xpack.slo.item.actions.manageRules', {
defaultMessage: 'Manage rules',
}),
'data-test-subj': 'sloActionsManageRules',
enabled: (slo: SLOWithSummaryResponse) =>
!!permissions?.hasAllWriteRequested && !isRemote(slo),
onClick: (slo: SLOWithSummaryResponse) => {
const locator = locators.get<RulesParams>(rulesLocatorID);
locator?.navigate({ params: { sloId: slo.id } }, { replace: false });
},
},
{
type: 'icon',
icon: (slo: SLOWithSummaryResponse) => (slo.enabled ? 'stop' : 'play'),
name: (slo: SLOWithSummaryResponse) =>
buildActionName(
slo.enabled
? i18n.translate('xpack.slo.item.actions.disable', {
defaultMessage: 'Disable',
})
: i18n.translate('xpack.slo.item.actions.enable', {
defaultMessage: 'Enable',
})
)(slo),
description: (slo: SLOWithSummaryResponse) =>
slo.enabled
? i18n.translate('xpack.slo.item.actions.disable', {
defaultMessage: 'Disable',
})
: i18n.translate('xpack.slo.item.actions.enable', {
defaultMessage: 'Enable',
}),
'data-test-subj': 'sloActionsManage',
enabled: (slo: SLOWithSummaryResponse) =>
(permissions?.hasAllWriteRequested && !isRemote(slo)) || hasRemoteKibanaUrl(slo),
onClick: (slo: SLOWithSummaryResponse) => {
const isEnabled = slo.enabled;
const remoteUrl = isEnabled
? createRemoteSloDisableUrl(slo, spaceId)
: createRemoteSloEnableUrl(slo, spaceId);
if (!!remoteUrl) {
window.open(remoteUrl, '_blank');
} else {
triggerAction({ item: slo, type: isEnabled ? 'disable' : 'enable' });
}
},
},
{
type: 'icon',
icon: 'copy',
name: buildActionName(
i18n.translate('xpack.slo.item.actions.clone', {
defaultMessage: 'Clone',
})
),
description: i18n.translate('xpack.slo.item.actions.clone', {
defaultMessage: 'Clone',
}),
'data-test-subj': 'sloActionsClone',
enabled: (slo: SLOWithSummaryResponse) =>
(permissions?.hasAllWriteRequested && !isRemote(slo)) || hasRemoteKibanaUrl(slo),
onClick: (slo: SLOWithSummaryResponse) => {
triggerAction({ item: slo, type: 'clone' });
},
},
{
type: 'icon',
icon: 'trash',
name: buildActionName(
i18n.translate('xpack.slo.item.actions.delete', {
defaultMessage: 'Delete',
})
),
description: i18n.translate('xpack.slo.item.actions.delete', {
defaultMessage: 'Delete',
}),
'data-test-subj': 'sloActionsDelete',
enabled: (slo: SLOWithSummaryResponse) =>
(permissions?.hasAllWriteRequested && !isRemote(slo)) || hasRemoteKibanaUrl(slo),
onClick: (slo: SLOWithSummaryResponse) => {
const remoteDeleteUrl = createRemoteSloDeleteUrl(slo, spaceId);
if (!!remoteDeleteUrl) {
window.open(remoteDeleteUrl, '_blank');
} else {
triggerAction({ item: slo, type: 'delete' });
}
},
},
{
type: 'icon',
icon: 'refresh',
name: buildActionName(
i18n.translate('xpack.slo.item.actions.reset', {
defaultMessage: 'Reset',
})
),
description: i18n.translate('xpack.slo.item.actions.reset', {
defaultMessage: 'Reset',
}),
'data-test-subj': 'sloActionsReset',
enabled: (slo: SLOWithSummaryResponse) =>
(permissions?.hasAllWriteRequested && !isRemote(slo)) || hasRemoteKibanaUrl(slo),
onClick: (slo: SLOWithSummaryResponse) => {
const remoteResetUrl = createRemoteSloResetUrl(slo, spaceId);
if (!!remoteResetUrl) {
window.open(remoteResetUrl, '_blank');
} else {
triggerAction({ item: slo, type: 'reset' });
}
},
},
];
const columns: Array<EuiBasicTableColumn<SLOWithSummaryResponse>> = [
{
field: 'status',
name: 'Status',
render: (_, slo: SLOWithSummaryResponse) => (
<EuiFlexGroup direction="row" gutterSize="s">
<SloStatusBadge slo={slo} />
<SloStateBadge slo={slo} />
<SloRemoteBadge slo={slo} />
</EuiFlexGroup>
),
},
{
field: 'alerts',
name: 'Alerts',
truncateText: true,
width: '5%',
render: (_, slo: SLOWithSummaryResponse) => (
<>
<SloRulesBadge
rules={rulesBySlo?.[slo.id]}
onClick={() => setSloToAddRule(slo)}
isRemote={!!slo.remote}
/>
<SloActiveAlertsBadge
slo={slo}
activeAlerts={activeAlertsBySlo.get(slo)}
viewMode="compact"
/>
</>
),
},
{
field: 'name',
name: 'Name',
width: '15%',
truncateText: { lines: 2 },
'data-test-subj': 'sloItem',
render: (_, slo: SLOWithSummaryResponse) => {
const sloDetailsUrl = basePath.prepend(
paths.sloDetails(slo.id, slo.instanceId, slo.remote?.remoteName)
);
return (
<EuiToolTip position="top" content={slo.name} display="block">
<EuiText size="s">
<a data-test-subj="o11ySloListItemLink" href={sloDetailsUrl}>
{slo.name}
</a>
</EuiText>
</EuiToolTip>
);
},
},
{
field: 'tags',
name: 'Tags',
render: (tags: string[]) => <SloTagsList tags={tags} color="default" />,
},
{
field: 'instance',
name: 'Instance',
render: (_, slo: SLOWithSummaryResponse) => {
const groups = [slo.groupBy].flat();
return !groups.includes(ALL_VALUE) ? (
<SLOGroupings slo={slo} direction="column" gutterSize={'none'} />
) : (
<span>{NOT_AVAILABLE_LABEL}</span>
);
},
},
{
field: 'objective',
name: 'Objective',
render: (_, slo: SLOWithSummaryResponse) => numeral(slo.objective.target).format('0.00%'),
},
{
field: 'sli',
name: 'SLI value',
truncateText: true,
render: (_, slo: SLOWithSummaryResponse) =>
slo.summary.status === 'NO_DATA'
? NOT_AVAILABLE_LABEL
: numeral(slo.summary.sliValue).format(percentFormat),
},
{
field: 'historicalSli',
name: 'Historical SLI',
render: (_, slo: SLOWithSummaryResponse) => {
const isSloFailed = ['VIOLATED', 'DEGRADING'].includes(slo.summary.status);
const historicalSliData = formatHistoricalData(
historicalSummaries.find(
(historicalSummary) =>
historicalSummary.sloId === slo.id && historicalSummary.instanceId === slo.instanceId
)?.data,
'sli_value'
);
return (
<SloSparkline
chart="line"
id="sli_history"
size="compact"
state={isSloFailed ? 'error' : 'success'}
data={historicalSliData}
isLoading={historicalSummaryLoading}
/>
);
},
},
{
field: 'errorBudgetRemaining',
name: 'Budget remaining',
truncateText: true,
render: (_, slo: SLOWithSummaryResponse) =>
slo.summary.status === 'NO_DATA'
? NOT_AVAILABLE_LABEL
: numeral(slo.summary.errorBudget.remaining).format(percentFormat),
},
{
field: 'historicalErrorBudgetRemaining',
name: 'Historical budget remaining',
render: (_, slo: SLOWithSummaryResponse) => {
const isSloFailed = ['VIOLATED', 'DEGRADING'].includes(slo.summary.status);
const errorBudgetBurnDownData = formatHistoricalData(
historicalSummaries.find(
(historicalSummary) =>
historicalSummary.sloId === slo.id && historicalSummary.instanceId === slo.instanceId
)?.data,
'error_budget_remaining'
);
return (
<SloSparkline
chart="area"
id="error_budget_burn_down"
state={isSloFailed ? 'error' : 'success'}
size="compact"
data={errorBudgetBurnDownData}
isLoading={historicalSummaryLoading}
/>
);
},
},
{
name: 'Actions',
actions,
width: '5%',
},
];
if (!loading && !error && sloList.length === 0) {
return <SloListEmpty />;
}
if (!loading && error) {
return <SloListError />;
}
return (
<>
<EuiBasicTable<SLOWithSummaryResponse>
items={sloList}
columns={columns}
loading={loading}
noItemsMessage={loading ? LOADING_SLOS_LABEL : NO_SLOS_FOUND}
tableLayout="auto"
/>
{sloToAddRule ? (
<RuleFormFlyout
plugins={{ ...services, ruleTypeRegistry, actionTypeRegistry }}
consumer={sloFeatureId}
filteredRuleTypes={filteredRuleTypes}
ruleTypeId={SLO_BURN_RATE_RULE_TYPE_ID}
initialValues={{
name: `${sloToAddRule.name} burn rate rule`,
params: { sloId: sloToAddRule.id },
}}
onSubmit={handleSavedRule}
onCancel={() => {
setSloToAddRule(undefined);
}}
shouldUseRuleProducer
/>
) : null}
</>
);
}