Skip to content

Commit de6ea00

Browse files
baileycash-elastickibanamachineshahzad31
authored
[8.x] [SLO]: Add filtering to SLO Management table, improve UX (#216040) (#218171)
# Backport This will backport the following commits from `main` to `8.x`: - [[SLO]: Add filtering to SLO Management table, improve UX (#216040)](#216040) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Bailey Cash","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-03-27T22:36:16Z","message":"[SLO]: Add filtering to SLO Management table, improve UX (#216040)\n\n## Summary\n\nResolves #214258 \n\n- Updates search bar to utilize UnifiedSearchBar\n- Adds ability to filter SLOs by tags (OR operator)\n- Makes improvements to version display\n\n![Screenshot 2025-03-26 at 2 55\n01 PM](https://github.com/user-attachments/assets/cf8c19e4-7a9f-4f2e-bd5d-b820b8f9bf23)\n![Screenshot 2025-03-26 at 2 54\n20 PM](https://github.com/user-attachments/assets/46e968ff-352a-4f4e-b762-a96c727c08f4)\n\n---------\n\nCo-authored-by: kdelemme <[email protected]>\nCo-authored-by: Elastic Machine <[email protected]>\nCo-authored-by: kibanamachine <[email protected]>","sha":"40e95f00f1b2f3b2ba76f958727e9bea24424679","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:skip","Team:obs-ux-management","v9.1.0"],"title":"[SLO]: Add filtering to SLO Management table, improve UX","number":216040,"url":"https://github.com/elastic/kibana/pull/216040","mergeCommit":{"message":"[SLO]: Add filtering to SLO Management table, improve UX (#216040)\n\n## Summary\n\nResolves #214258 \n\n- Updates search bar to utilize UnifiedSearchBar\n- Adds ability to filter SLOs by tags (OR operator)\n- Makes improvements to version display\n\n![Screenshot 2025-03-26 at 2 55\n01 PM](https://github.com/user-attachments/assets/cf8c19e4-7a9f-4f2e-bd5d-b820b8f9bf23)\n![Screenshot 2025-03-26 at 2 54\n20 PM](https://github.com/user-attachments/assets/46e968ff-352a-4f4e-b762-a96c727c08f4)\n\n---------\n\nCo-authored-by: kdelemme <[email protected]>\nCo-authored-by: Elastic Machine <[email protected]>\nCo-authored-by: kibanamachine <[email protected]>","sha":"40e95f00f1b2f3b2ba76f958727e9bea24424679"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/216040","number":216040,"mergeCommit":{"message":"[SLO]: Add filtering to SLO Management table, improve UX (#216040)\n\n## Summary\n\nResolves #214258 \n\n- Updates search bar to utilize UnifiedSearchBar\n- Adds ability to filter SLOs by tags (OR operator)\n- Makes improvements to version display\n\n![Screenshot 2025-03-26 at 2 55\n01 PM](https://github.com/user-attachments/assets/cf8c19e4-7a9f-4f2e-bd5d-b820b8f9bf23)\n![Screenshot 2025-03-26 at 2 54\n20 PM](https://github.com/user-attachments/assets/46e968ff-352a-4f4e-b762-a96c727c08f4)\n\n---------\n\nCo-authored-by: kdelemme <[email protected]>\nCo-authored-by: Elastic Machine <[email protected]>\nCo-authored-by: kibanamachine <[email protected]>","sha":"40e95f00f1b2f3b2ba76f958727e9bea24424679"}}]}] BACKPORT--> --------- Co-authored-by: kibanamachine <[email protected]> Co-authored-by: Shahzad <[email protected]>
1 parent 1761809 commit de6ea00

File tree

12 files changed

+261
-110
lines changed

12 files changed

+261
-110
lines changed

oas_docs/output/kibana.serverless.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46071,6 +46071,11 @@ paths:
4607146071
name: includeOutdatedOnly
4607246072
schema:
4607346073
type: boolean
46074+
- description: Specify which SLO tags to query by (comma-separated list)
46075+
in: query
46076+
name: tags
46077+
schema:
46078+
type: string
4607446079
- description: Filters the SLOs by name
4607546080
example: my service availability
4607646081
in: query

x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/find_definition.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const findSloDefinitionsParamsSchema = t.partial({
1212
query: t.partial({
1313
search: t.string,
1414
includeOutdatedOnly: toBooleanRt,
15+
tags: t.string,
1516
page: t.string,
1617
perPage: t.string,
1718
}),

x-pack/solutions/observability/plugins/slo/docs/openapi/slo/bundled.json

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,113 @@
677677
}
678678
}
679679
},
680+
"/s/{spaceId}/internal/observability/slos/_definitions": {
681+
"get": {
682+
"summary": "Get the SLO definitions",
683+
"operationId": "getDefinitionsOp",
684+
"description": "You must have the `read` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges.\n",
685+
"tags": [
686+
"slo"
687+
],
688+
"parameters": [
689+
{
690+
"$ref": "#/components/parameters/kbn_xsrf"
691+
},
692+
{
693+
"$ref": "#/components/parameters/space_id"
694+
},
695+
{
696+
"name": "includeOutdatedOnly",
697+
"in": "query",
698+
"description": "Indicates if the API returns only outdated SLO or all SLO definitions",
699+
"schema": {
700+
"type": "boolean"
701+
},
702+
"example": true
703+
},
704+
{
705+
"name": "tags",
706+
"in": "query",
707+
"description": "Specify which SLO tags to query by (comma-separated values)",
708+
"schema": {
709+
"type": "string"
710+
},
711+
"example": true
712+
},
713+
{
714+
"name": "search",
715+
"in": "query",
716+
"description": "Filters the SLOs by name",
717+
"schema": {
718+
"type": "string"
719+
},
720+
"example": "my service availability"
721+
},
722+
{
723+
"name": "page",
724+
"in": "query",
725+
"description": "The page to use for pagination, must be greater or equal than 1",
726+
"schema": {
727+
"type": "number"
728+
},
729+
"example": 1
730+
},
731+
{
732+
"name": "perPage",
733+
"in": "query",
734+
"description": "Number of SLOs returned by page",
735+
"schema": {
736+
"type": "integer",
737+
"default": 100,
738+
"maximum": 1000
739+
},
740+
"example": 100
741+
}
742+
],
743+
"responses": {
744+
"200": {
745+
"description": "Successful request",
746+
"content": {
747+
"application/json": {
748+
"schema": {
749+
"$ref": "#/components/schemas/find_slo_definitions_response"
750+
}
751+
}
752+
}
753+
},
754+
"400": {
755+
"description": "Bad request",
756+
"content": {
757+
"application/json": {
758+
"schema": {
759+
"$ref": "#/components/schemas/400_response"
760+
}
761+
}
762+
}
763+
},
764+
"401": {
765+
"description": "Unauthorized response",
766+
"content": {
767+
"application/json": {
768+
"schema": {
769+
"$ref": "#/components/schemas/401_response"
770+
}
771+
}
772+
}
773+
},
774+
"403": {
775+
"description": "Unauthorized response",
776+
"content": {
777+
"application/json": {
778+
"schema": {
779+
"$ref": "#/components/schemas/403_response"
780+
}
781+
}
782+
}
783+
}
784+
}
785+
}
786+
},
680787
"/s/{spaceId}/api/observability/slos/_delete_instances": {
681788
"post": {
682789
"summary": "Batch delete rollup and summary data",

x-pack/solutions/observability/plugins/slo/docs/openapi/slo/bundled.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1346,7 +1346,7 @@ components:
13461346
example: Bad Request
13471347
message:
13481348
type: string
1349-
example: 'Invalid value ''foo'' supplied to: [...]'
1349+
example: "Invalid value 'foo' supplied to: [...]"
13501350
401_response:
13511351
title: Unauthorized
13521352
type: object

x-pack/solutions/observability/plugins/slo/docs/openapi/slo/paths/s@{spaceid}@api@slos@_definitions.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ get:
1515
schema:
1616
type: boolean
1717
example: true
18+
- name: tags
19+
in: query
20+
description: Filters the SLOs by tag
21+
schema:
22+
type: string
1823
- name: search
1924
in: query
2025
description: Filters the SLOs by name
@@ -29,7 +34,7 @@ get:
2934
example: 1
3035
- name: perPage
3136
in: query
32-
description: Number of SLOs returned by page
37+
description: Number of SLOs returned by page
3338
schema:
3439
type: integer
3540
default: 100

x-pack/solutions/observability/plugins/slo/public/hooks/query_key_factory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const sloKeys = {
5757
page: number;
5858
perPage: number;
5959
includeOutdatedOnly: boolean;
60+
validTags: string;
6061
}) => [...sloKeys.allDefinitions(), params],
6162
globalDiagnosis: () => [...sloKeys.all, 'globalDiagnosis'] as const,
6263
health: (list: Array<{ sloId: string; sloInstanceId: string }>) =>

x-pack/solutions/observability/plugins/slo/public/hooks/use_fetch_slo_definitions.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,32 @@ export interface UseFetchSloDefinitionsResponse {
2121
interface SLODefinitionParams {
2222
name?: string;
2323
includeOutdatedOnly?: boolean;
24+
tags?: string[];
2425
page?: number;
2526
perPage?: number;
2627
}
2728

2829
export function useFetchSloDefinitions({
2930
name = '',
3031
includeOutdatedOnly = false,
32+
tags = [],
3133
page = 1,
3234
perPage = 100,
3335
}: SLODefinitionParams): UseFetchSloDefinitionsResponse {
3436
const { sloClient } = usePluginContext();
3537
const search = name.endsWith('*') ? name : `${name}*`;
38+
const validTags = tags.filter((tag) => !!tag).join();
3639

3740
const { isLoading, isError, isSuccess, data, refetch } = useQuery({
38-
queryKey: sloKeys.definitions({ search, page, perPage, includeOutdatedOnly }),
41+
queryKey: sloKeys.definitions({ search, page, perPage, includeOutdatedOnly, validTags }),
3942
queryFn: async ({ signal }) => {
4043
try {
4144
return await sloClient.fetch('GET /api/observability/slos/_definitions 2023-10-31', {
4245
params: {
4346
query: {
4447
...(search !== undefined && { search }),
4548
...(includeOutdatedOnly !== undefined && { includeOutdatedOnly }),
49+
...(validTags?.length && { tags: validTags }),
4650
...(page !== undefined && { page: String(page) }),
4751
...(perPage !== undefined && { perPage: String(perPage) }),
4852
},

x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_search_bar.tsx

Lines changed: 71 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,72 +7,85 @@
77

88
import React, { useState } from 'react';
99
import { i18n } from '@kbn/i18n';
10-
import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiButton } from '@elastic/eui';
10+
import { EuiComboBox, EuiComboBoxOptionOption, EuiText } from '@elastic/eui';
11+
import { observabilityAppId } from '@kbn/observability-shared-plugin/common';
12+
import { useFetchSLOSuggestions } from '../../slo_edit/hooks/use_fetch_suggestions';
13+
import { useKibana } from '../../../hooks/use_kibana';
1114

1215
interface Props {
13-
initialSearch?: string;
16+
filters: {
17+
search: string;
18+
tags: string[];
19+
};
20+
setFilters: Function;
1421
onRefresh: () => void;
15-
onSearch: (search: string) => void;
1622
}
1723

18-
export function SloManagementSearchBar({ onSearch, onRefresh, initialSearch = '' }: Props) {
19-
const [tempSearch, setTempSearch] = useState<string>(initialSearch);
20-
const [search, setSearch] = useState<string>(initialSearch);
21-
const refreshOrUpdateSearch = () => {
22-
if (tempSearch !== search) {
23-
setSearch(tempSearch);
24-
onSearch(tempSearch);
25-
} else {
26-
onRefresh();
27-
}
28-
};
29-
30-
const handleClick = (event: React.ChangeEvent<HTMLInputElement>) =>
31-
setTempSearch(event.target.value);
24+
export function SloManagementSearchBar({ filters, setFilters, onRefresh }: Props) {
25+
const {
26+
unifiedSearch: {
27+
ui: { SearchBar },
28+
},
29+
} = useKibana().services;
3230

33-
const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
34-
if (event.key === 'Enter') {
35-
refreshOrUpdateSearch();
36-
}
37-
};
31+
const { suggestions } = useFetchSLOSuggestions();
32+
const [selectedOptions, setSelectedOptions] = useState<Array<EuiComboBoxOptionOption<string>>>(
33+
[]
34+
);
3835

3936
return (
40-
<EuiFlexGroup gutterSize="s">
41-
<EuiFlexItem>
42-
<EuiFieldSearch
43-
data-test-subj="o11ySloDefinitionsFieldSearch"
44-
fullWidth
45-
value={tempSearch}
46-
onChange={handleClick}
47-
onKeyDown={handleKeyPress}
37+
<SearchBar
38+
appName={observabilityAppId}
39+
placeholder={i18n.translate('xpack.slo.sloDefinitions.filterByName', {
40+
defaultMessage: 'Filter by name',
41+
})}
42+
isAutoRefreshDisabled
43+
disableQueryLanguageSwitcher
44+
nonKqlMode="text"
45+
showQueryMenu={false}
46+
showDatePicker={false}
47+
showSavedQueryControls={false}
48+
showFilterBar={false}
49+
query={{ query: filters.search, language: 'text' }}
50+
onQuerySubmit={({ query: value }) => {
51+
setFilters({ search: value?.query, tags: filters.tags });
52+
}}
53+
onRefresh={onRefresh}
54+
renderQueryInputAppend={() => (
55+
<EuiComboBox
56+
aria-label={filterTagsLabel}
57+
placeholder={filterTagsLabel}
58+
delimiter=","
59+
options={suggestions?.tags ? [existOption, ...suggestions?.tags] : []}
60+
selectedOptions={selectedOptions}
61+
onChange={(newOptions) => {
62+
setSelectedOptions(newOptions);
63+
setFilters({ search: filters.search, tags: newOptions.map((option) => option.value) });
64+
}}
65+
isClearable={true}
66+
data-test-subj="filter-slos-by-tag"
4867
/>
49-
</EuiFlexItem>
50-
<EuiFlexItem grow={0}>
51-
{search === tempSearch && (
52-
<EuiButton
53-
data-test-subj="o11ySloDefinitionsRefreshButton"
54-
iconType="refresh"
55-
onClick={refreshOrUpdateSearch}
56-
>
57-
{i18n.translate('xpack.slo.sloDefinitions.refreshButtonLabel', {
58-
defaultMessage: 'Refresh',
59-
})}
60-
</EuiButton>
61-
)}
62-
{search !== tempSearch && (
63-
<EuiButton
64-
data-test-subj="o11ySloDefinitionsUpdateButton"
65-
iconType="kqlFunction"
66-
color="success"
67-
fill
68-
onClick={refreshOrUpdateSearch}
69-
>
70-
{i18n.translate('xpack.slo.sloDefinitions.updateButtonLabel', {
71-
defaultMessage: 'Update',
72-
})}
73-
</EuiButton>
74-
)}
75-
</EuiFlexItem>
76-
</EuiFlexGroup>
68+
)}
69+
/>
7770
);
7871
}
72+
73+
const filterTagsLabel = i18n.translate('xpack.slo.sloDefinitions.filterByTag', {
74+
defaultMessage: 'Filter tags',
75+
});
76+
77+
const existOption = {
78+
prepend: (
79+
<EuiText size="s">
80+
<strong>
81+
<em>
82+
{i18n.translate('xpack.slo.sloDefinitions.tagOptions.exists', {
83+
defaultMessage: 'Exists',
84+
})}
85+
</em>
86+
</strong>
87+
</EuiText>
88+
),
89+
label: '',
90+
value: '*',
91+
};

0 commit comments

Comments
 (0)