Skip to content

Commit 24c24f0

Browse files
authoredFeb 21, 2024
Display search unavailable error if search is not configured (stolostron#3311)
Signed-off-by: zlayne <zlayne@redhat.com>
1 parent 1ae0a92 commit 24c24f0

File tree

3 files changed

+148
-22
lines changed

3 files changed

+148
-22
lines changed
 

‎frontend/src/lib/nock-util.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* Copyright Contributors to the Open Cluster Management project */
22

33
/* istanbul ignore file */
4+
import { diff } from 'deep-diff'
45
import isEqual from 'lodash/isEqual'
56
import set from 'lodash/set'
6-
import { diff } from 'deep-diff'
77
import nock from 'nock'
88
import StackTrace from 'stacktrace-js'
99
import { Url } from 'url'
@@ -22,8 +22,8 @@ import {
2222
} from '../resources'
2323
import { AnsibleTowerInventoryList } from '../resources/ansible-inventory'
2424
import { APIResourceNames } from './api-resource-list'
25-
import { apiSearchUrl, ISearchResult, SearchQuery } from './search'
2625
import { OperatorCheckResponse, SupportedOperator } from './operatorCheck'
26+
import { apiSearchUrl, ISearchResult, SearchQuery } from './search'
2727

2828
export type ISearchRelatedResult = {
2929
data: {
@@ -618,6 +618,11 @@ const mockApiPathList: APIResourceNames = {
618618
pluralName: 'applications',
619619
},
620620
},
621+
'search.open-cluster-management.io/v1alpha1': {
622+
Search: {
623+
pluralName: 'searches',
624+
},
625+
},
621626
}
622627

623628
const mockOperatorCheckResponse: OperatorCheckResponse = {

‎frontend/src/routes/Home/Overview/SavedSearchesCard.test.tsx

+87
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,36 @@
22

33
import { MockedProvider } from '@apollo/client/testing'
44
import { render, waitFor } from '@testing-library/react'
5+
import { GraphQLError } from 'graphql'
56
import { createBrowserHistory } from 'history'
67
import { Router } from 'react-router-dom'
78
import { RecoilRoot } from 'recoil'
89
import { Settings, settingsState } from '../../../../src/atoms'
10+
import { nockIgnoreApiPaths } from '../../../lib/nock-util'
911
import { SavedSearch } from '../../../resources'
1012
import { SearchResultCountDocument } from '../Search/search-sdk/search-sdk'
1113
import SavedSearchesCard from './SavedSearchesCard'
1214

15+
jest.mock('../../../resources', () => ({
16+
listResources: jest.fn(() => ({
17+
promise: Promise.resolve([
18+
{
19+
status: {
20+
conditions: [
21+
{
22+
lastTransitionTime: '2024-02-21T19:34:39Z',
23+
message: 'Check status of deployment: search-api',
24+
reason: 'NoPodsFound',
25+
status: 'False',
26+
type: 'Ready--search-api',
27+
},
28+
],
29+
},
30+
},
31+
]),
32+
})),
33+
}))
34+
1335
const mockSettings: Settings = {
1436
SEARCH_RESULT_LIMIT: '1000',
1537
}
@@ -83,6 +105,49 @@ const mocks = [
83105
},
84106
]
85107

108+
const errorMock = [
109+
{
110+
request: {
111+
query: SearchResultCountDocument,
112+
variables: {
113+
input: [
114+
{
115+
keywords: [],
116+
filters: [
117+
{
118+
property: 'kind',
119+
values: ['Pod'],
120+
},
121+
],
122+
limit: 1000,
123+
},
124+
{
125+
keywords: [],
126+
filters: [
127+
{
128+
property: 'label',
129+
values: ['app=search'],
130+
},
131+
{
132+
property: 'kind',
133+
values: ['Pod'],
134+
},
135+
{
136+
property: 'namespace',
137+
values: ['open-cluster-management'],
138+
},
139+
],
140+
limit: 1000,
141+
},
142+
],
143+
},
144+
},
145+
result: {
146+
errors: [new GraphQLError('Error getting overview data')],
147+
},
148+
},
149+
]
150+
86151
describe('SavedSearchesCard', () => {
87152
test('Renders valid SavedSearchesCard with no saved searches', async () => {
88153
const { getByText } = render(
@@ -137,4 +202,26 @@ describe('SavedSearchesCard', () => {
137202
await waitFor(() => expect(getByText('10')).toBeTruthy())
138203
await waitFor(() => expect(getByText('2')).toBeTruthy())
139204
})
205+
206+
test('Renders erro correctly when search is disabled', async () => {
207+
nockIgnoreApiPaths()
208+
const { getByText } = render(
209+
<RecoilRoot
210+
initializeState={(snapshot) => {
211+
snapshot.set(settingsState, mockSettings)
212+
}}
213+
>
214+
<Router history={createBrowserHistory()}>
215+
<MockedProvider mocks={errorMock}>
216+
<SavedSearchesCard isUserPreferenceLoading={false} savedSearches={savedSearches} />
217+
</MockedProvider>
218+
</Router>
219+
</RecoilRoot>
220+
)
221+
222+
// Check header strings
223+
await waitFor(() => expect(getByText('Saved searches')).toBeTruthy())
224+
await waitFor(() => expect(getByText('This view is disabled with the current configuration.')).toBeTruthy())
225+
await waitFor(() => expect(getByText('Enable the search service to see this view.')).toBeTruthy())
226+
})
140227
})

‎frontend/src/routes/Home/Overview/SavedSearchesCard.tsx

+54-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* Copyright Contributors to the Open Cluster Management project */
2+
import { V1CustomResourceDefinitionCondition } from '@kubernetes/client-node'
23
import {
34
Card,
45
CardBody,
@@ -14,28 +15,31 @@ import {
1415
Title,
1516
} from '@patternfly/react-core'
1617
import { CogIcon, ExclamationCircleIcon } from '@patternfly/react-icons'
17-
import { Fragment } from 'react'
18+
import { Fragment, useEffect, useState } from 'react'
1819
import { Link } from 'react-router-dom'
1920
import { useTranslation } from '../../../lib/acm-i18next'
2021
import { NavigationPath } from '../../../NavigationPath'
21-
import { SavedSearch } from '../../../resources'
22+
import { IResource, listResources, SavedSearch } from '../../../resources'
2223
import { useSharedAtoms } from '../../../shared-recoil'
2324
import { convertStringToQuery } from '../Search/search-helper'
2425
import { searchClient } from '../Search/search-sdk/search-client'
2526
import { useSearchResultCountQuery } from '../Search/search-sdk/search-sdk'
2627

27-
const CardHeader = () => {
28+
const CardHeader = (props: { isSearchDisabled: boolean }) => {
29+
const { isSearchDisabled } = props
2830
const { t } = useTranslation()
2931
return (
3032
<CardTitle>
3133
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
3234
<span>{t('Saved searches')}</span>
33-
<Link style={{ display: 'flex', alignItems: 'center' }} to={NavigationPath.search}>
34-
<CogIcon />
35-
<Text style={{ paddingLeft: '.25rem' }} component={TextVariants.small}>
36-
{t('Manage')}
37-
</Text>
38-
</Link>
35+
{!isSearchDisabled && (
36+
<Link style={{ display: 'flex', alignItems: 'center' }} to={NavigationPath.search}>
37+
<CogIcon />
38+
<Text style={{ paddingLeft: '.25rem' }} component={TextVariants.small}>
39+
{t('Manage')}
40+
</Text>
41+
</Link>
42+
)}
3943
</div>
4044
</CardTitle>
4145
)
@@ -48,18 +52,44 @@ export default function SavedSearchesCard(
4852
const { t } = useTranslation()
4953
const { useSearchResultLimit } = useSharedAtoms()
5054
const searchResultLimit = useSearchResultLimit()
55+
const [isSearchDisabledLoading, setIsSearchDisabledLoading] = useState<boolean>(false)
56+
const [isSearchDisabled, setIsSearchDisabled] = useState<boolean>()
5157

5258
const { data, error, loading } = useSearchResultCountQuery({
5359
variables: { input: savedSearches.map((query) => convertStringToQuery(query.searchText, searchResultLimit)) },
5460
skip: isUserPreferenceLoading || savedSearches.length === 0,
5561
client: process.env.NODE_ENV === 'test' ? undefined : searchClient,
5662
})
5763

58-
if (isUserPreferenceLoading || loading) {
64+
useEffect(() => {
65+
if (error) {
66+
setIsSearchDisabledLoading(true)
67+
listResources<IResource>({
68+
apiVersion: 'search.open-cluster-management.io/v1alpha1',
69+
kind: 'Search',
70+
})
71+
.promise.then((response) => {
72+
const operatorConditions: V1CustomResourceDefinitionCondition[] = response[0]?.status?.conditions ?? []
73+
const searchApiCondition = operatorConditions.find((c: V1CustomResourceDefinitionCondition) => {
74+
return c.type.toLowerCase() === 'ready--search-api' && c.status === 'False'
75+
})
76+
if (searchApiCondition) {
77+
setIsSearchDisabled(true)
78+
}
79+
setIsSearchDisabledLoading(false)
80+
})
81+
.catch((err) => {
82+
console.error('Error getting resource: ', err)
83+
setIsSearchDisabledLoading(false)
84+
})
85+
}
86+
}, [error])
87+
88+
if (isUserPreferenceLoading || loading || isSearchDisabledLoading) {
5989
return (
6090
<div>
6191
<Card isRounded>
62-
<CardHeader />
92+
<CardHeader isSearchDisabled={false} />
6393
<CardBody>
6494
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
6595
<Skeleton width="45%" />
@@ -75,19 +105,23 @@ export default function SavedSearchesCard(
75105
</div>
76106
)
77107
} else if (error) {
108+
const searchDisabledTitle = 'This view is disabled with the current configuration.'
109+
const searchDisabledMessage = 'Enable the search service to see this view.'
78110
return (
79111
<Card isRounded>
80-
<CardHeader />
112+
<CardHeader isSearchDisabled />
81113
<EmptyState style={{ paddingTop: 0, marginTop: 'auto' }}>
82-
<EmptyStateIcon
83-
style={{ fontSize: '36px', marginBottom: '1rem' }}
84-
icon={ExclamationCircleIcon}
85-
color={'var(--pf-global--danger-color--100)'}
86-
/>
114+
{!isSearchDisabled && (
115+
<EmptyStateIcon
116+
style={{ fontSize: '36px', marginBottom: '1rem' }}
117+
icon={ExclamationCircleIcon}
118+
color={'var(--pf-global--danger-color--100)'}
119+
/>
120+
)}
87121
<Title size="md" headingLevel="h4">
88-
{t('Error occurred while getting the result count.')}
122+
{isSearchDisabled ? searchDisabledTitle : t('Error occurred while getting the result count.')}
89123
</Title>
90-
<EmptyStateBody>{error.message}</EmptyStateBody>
124+
<EmptyStateBody>{isSearchDisabled ? searchDisabledMessage : error.message}</EmptyStateBody>
91125
</EmptyState>
92126
</Card>
93127
)
@@ -116,7 +150,7 @@ export default function SavedSearchesCard(
116150
return (
117151
<div>
118152
<Card isRounded>
119-
<CardHeader />
153+
<CardHeader isSearchDisabled={false} />
120154
<CardBody>
121155
<div style={{ display: 'grid', gridTemplateColumns: 'auto auto auto auto auto', columnGap: 16 }}>
122156
{savedSearches.map((savedSearch: SavedSearch, index: number) => {

0 commit comments

Comments
 (0)
Failed to load comments.