Skip to content

Commit 600e5c0

Browse files
authored
Check if search and agent API routes are available. (#2269)
* Check if search and agent API routes are available. * fix lint * Changeset
1 parent f214711 commit 600e5c0

File tree

14 files changed

+359
-44
lines changed

14 files changed

+359
-44
lines changed

.changeset/busy-worlds-matter.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@orchestrator-ui/orchestrator-ui-components': major
3+
---
4+
5+
Detect if search and agent endpoints are available. Show a message showing how to setup the routes correctly if they are not available.

packages/orchestrator-ui-components/src/components/WfoAgent/WfoAgent/WfoAgent.tsx

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import {
1111
import { CopilotSidebar } from '@copilotkit/react-ui';
1212
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
1313

14+
import { WfoAvailabilityCheck } from '@/components/WfoAvailabilityCheck';
1415
import { WfoSearchResults } from '@/components/WfoSearchPage/WfoSearchResults';
16+
import { useAgentAvailability } from '@/hooks/useBackendAvailability';
1517
import { AnySearchParameters, SearchResult } from '@/types';
1618

1719
import { ExportButton, ExportData } from '../ExportButton';
@@ -46,6 +48,8 @@ export function WfoAgent() {
4648
const t = useTranslations('agent');
4749
const tPage = useTranslations('agent.page');
4850

51+
const agentAvailability = useAgentAvailability();
52+
4953
const { state } = useCoAgent<SearchState>({
5054
name: 'query_agent',
5155
initialState,
@@ -84,44 +88,49 @@ export function WfoAgent() {
8488
});
8589

8690
return (
87-
<EuiFlexGroup gutterSize="l" alignItems="stretch">
88-
<EuiFlexItem grow={2}>
89-
<EuiText>
90-
<h1>{t('title')}</h1>
91-
</EuiText>
92-
93-
<EuiSpacer size="m" />
94-
95-
{results_data && results_data.action === 'view_results' && (
96-
<>
97-
{results_data.message && (
98-
<>
99-
<EuiText size="s">
100-
<p>{results_data.message}</p>
101-
</EuiText>
102-
<EuiSpacer size="s" />
103-
</>
104-
)}
105-
<WfoSearchResults
106-
results={results_data.results}
107-
loading={false}
108-
selectedRecordIndex={-1}
109-
onRecordSelect={() => {}}
110-
/>
111-
</>
112-
)}
113-
</EuiFlexItem>
114-
115-
<EuiFlexItem grow={1}>
116-
<CopilotSidebar
117-
defaultOpen
118-
clickOutsideToClose={false}
119-
labels={{
120-
title: tPage('copilot.title'),
121-
initial: tPage('copilot.initial'),
122-
}}
123-
/>
124-
</EuiFlexItem>
125-
</EuiFlexGroup>
91+
<WfoAvailabilityCheck
92+
featureType="agent"
93+
availability={agentAvailability}
94+
>
95+
<EuiFlexGroup gutterSize="l" alignItems="stretch">
96+
<EuiFlexItem grow={2}>
97+
<EuiText>
98+
<h1>{t('title')}</h1>
99+
</EuiText>
100+
101+
<EuiSpacer size="m" />
102+
103+
{results_data && results_data.action === 'view_results' && (
104+
<>
105+
{results_data.message && (
106+
<>
107+
<EuiText size="s">
108+
<p>{results_data.message}</p>
109+
</EuiText>
110+
<EuiSpacer size="s" />
111+
</>
112+
)}
113+
<WfoSearchResults
114+
results={results_data.results}
115+
loading={false}
116+
selectedRecordIndex={-1}
117+
onRecordSelect={() => {}}
118+
/>
119+
</>
120+
)}
121+
</EuiFlexItem>
122+
123+
<EuiFlexItem grow={1}>
124+
<CopilotSidebar
125+
defaultOpen
126+
clickOutsideToClose={false}
127+
labels={{
128+
title: tPage('copilot.title'),
129+
initial: tPage('copilot.initial'),
130+
}}
131+
/>
132+
</EuiFlexItem>
133+
</EuiFlexGroup>
134+
</WfoAvailabilityCheck>
126135
);
127136
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React, { FC, ReactNode } from 'react';
2+
3+
import { WfoBackendUnavailable } from '@/components/WfoBackendUnavailable';
4+
import { BackendFeatureStatus } from '@/hooks';
5+
6+
interface WfoAvailabilityCheckProps {
7+
featureType: 'search' | 'agent';
8+
availability: BackendFeatureStatus;
9+
children: ReactNode;
10+
}
11+
12+
export const WfoAvailabilityCheck: FC<WfoAvailabilityCheckProps> = ({
13+
featureType,
14+
availability,
15+
children,
16+
}) => {
17+
if (!availability.isLoading && !availability.isAvailable) {
18+
return (
19+
<WfoBackendUnavailable
20+
featureType={featureType}
21+
onRetry={() => window.location.reload()}
22+
/>
23+
);
24+
}
25+
26+
return <>{children}</>;
27+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { WfoAvailabilityCheck } from './WfoAvailabilityCheck';
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import React, { FC } from 'react';
2+
3+
import { useTranslations } from 'next-intl';
4+
5+
import {
6+
EuiButton,
7+
EuiCallOut,
8+
EuiCode,
9+
EuiSpacer,
10+
EuiText,
11+
} from '@elastic/eui';
12+
13+
interface WfoBackendUnavailableProps {
14+
featureType: 'search' | 'agent';
15+
onRetry?: () => void;
16+
}
17+
18+
export const WfoBackendUnavailable: FC<WfoBackendUnavailableProps> = ({
19+
featureType,
20+
onRetry,
21+
}) => {
22+
const t = useTranslations(`${featureType}.availability.unavailable`);
23+
24+
const getInstructionSteps = () => {
25+
if (featureType === 'search') {
26+
return [
27+
'setEnvironmentVariable',
28+
'checkVersion',
29+
'restartService',
30+
'checkDockerConfig',
31+
];
32+
} else {
33+
return [
34+
'setAgentEnvironment',
35+
'setSearchEnvironment',
36+
'checkVersion',
37+
'configureOpenAI',
38+
'restartService',
39+
'checkDockerConfig',
40+
];
41+
}
42+
};
43+
44+
const renderInstruction = (step: string) => {
45+
if (step === 'setEnvironmentVariable') {
46+
return (
47+
<>
48+
{t('instructions.setEnvironmentVariable.before')}
49+
<EuiCode>SEARCH_ENABLED=True</EuiCode>
50+
{t('instructions.setEnvironmentVariable.after')}
51+
</>
52+
);
53+
} else if (step === 'setAgentEnvironment') {
54+
return (
55+
<>
56+
{t('instructions.setAgentEnvironment.before')}
57+
<EuiCode>AGENT_ENABLED=True</EuiCode>
58+
{t('instructions.setAgentEnvironment.after')}
59+
</>
60+
);
61+
} else if (step === 'setSearchEnvironment') {
62+
return (
63+
<>
64+
{t('instructions.setSearchEnvironment.before')}
65+
<EuiCode>SEARCH_ENABLED=True</EuiCode>
66+
{t('instructions.setSearchEnvironment.after')}
67+
</>
68+
);
69+
} else {
70+
return t(`instructions.${step}`);
71+
}
72+
};
73+
74+
return (
75+
<EuiCallOut
76+
title={t('title')}
77+
color="warning"
78+
iconType="alert"
79+
data-testid={`backend-unavailable-${featureType}`}
80+
>
81+
<EuiText size="s">
82+
<ul>
83+
{getInstructionSteps().map((step, index) => (
84+
<li key={index}>{renderInstruction(step)}</li>
85+
))}
86+
</ul>
87+
</EuiText>
88+
89+
<EuiSpacer size="s" />
90+
91+
<EuiText size="s" color="subdued">
92+
{t('documentation')}
93+
</EuiText>
94+
95+
{onRetry && (
96+
<>
97+
<EuiSpacer size="m" />
98+
<EuiButton
99+
size="s"
100+
onClick={onRetry}
101+
data-testid={`retry-${featureType}-backend`}
102+
>
103+
{t('retryButton')}
104+
</EuiButton>
105+
</>
106+
)}
107+
</EuiCallOut>
108+
);
109+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { WfoBackendUnavailable } from './WfoBackendUnavailable';

packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearch/WfoSearch.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ import {
1616
} from '@elastic/eui';
1717

1818
import { WfoSubscription } from '@/components';
19+
import { WfoAvailabilityCheck } from '@/components/WfoAvailabilityCheck';
1920
import { WfoBadge } from '@/components/WfoBadges';
2021
import {
2122
ENTITY_TABS,
2223
isSubscriptionSearchResult,
2324
} from '@/components/WfoSearchPage/utils';
2425
import { TreeProvider } from '@/contexts';
2526
import { useOrchestratorTheme } from '@/hooks';
27+
import { useSearchAvailability } from '@/hooks/useBackendAvailability';
2628
import { useDebounce } from '@/hooks/useDebounce';
2729
import { useSearch } from '@/hooks/useSearch';
2830
import { useSearchPagination } from '@/hooks/useSearchPagination';
@@ -43,6 +45,8 @@ import {
4345
export const WfoSearch = () => {
4446
const t = useTranslations('search.page');
4547
const { theme } = useOrchestratorTheme();
48+
const searchAvailability = useSearchAvailability();
49+
4650
const {
4751
urlParams,
4852
query,
@@ -202,7 +206,10 @@ export const WfoSearch = () => {
202206
const { RESULTS_GROW, DETAIL_GROW } = LAYOUT_RATIOS;
203207

204208
return (
205-
<>
209+
<WfoAvailabilityCheck
210+
featureType="search"
211+
availability={searchAvailability}
212+
>
206213
<EuiTabs>
207214
{ENTITY_TABS.map((tab) => (
208215
<EuiTab
@@ -442,6 +449,6 @@ export const WfoSearch = () => {
442449
</EuiFlexGroup>
443450
</>
444451
)}
445-
</>
452+
</WfoAvailabilityCheck>
446453
);
447454
};

packages/orchestrator-ui-components/src/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
export * from './WfoBadges';
2+
export * from './WfoBackendUnavailable';
3+
export * from './WfoAvailabilityCheck';
24
export * from './WfoContentHeader';
35
export * from './WfoExpandableField';
46
export * from './WfoPageTemplate/WfoBreadcrumbs';

packages/orchestrator-ui-components/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from './useWithOrchestratorTheme';
1010
export * from './useWfoErrorMonitoring';
1111
export * from './useWfoSession';
1212
export * from './useGetOrchestratorConfig';
13+
export * from './useBackendAvailability';
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { GraphQLError } from 'graphql';
2+
3+
import { SerializedError } from '@reduxjs/toolkit';
4+
import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
5+
6+
import {
7+
useCheckAgentAvailabilityQuery,
8+
useCheckSearchAvailabilityQuery,
9+
} from '@/rtk/endpoints/availability';
10+
11+
export interface BackendFeatureStatus {
12+
isAvailable: boolean;
13+
isLoading: boolean;
14+
}
15+
16+
type RTKQueryError = FetchBaseQueryError | SerializedError | GraphQLError[];
17+
18+
const isNotFoundError = (error: RTKQueryError | undefined): boolean => {
19+
if (error && 'status' in error) {
20+
return error.status === 404;
21+
}
22+
return false;
23+
};
24+
25+
export const useSearchAvailability = (): BackendFeatureStatus => {
26+
const { isLoading, error } = useCheckSearchAvailabilityQuery();
27+
28+
if (isLoading) {
29+
return {
30+
isAvailable: false,
31+
isLoading: true,
32+
};
33+
}
34+
35+
if (error) {
36+
const isNotFound = isNotFoundError(error);
37+
return {
38+
isAvailable: !isNotFound,
39+
isLoading: false,
40+
};
41+
}
42+
43+
return {
44+
isAvailable: true,
45+
isLoading: false,
46+
};
47+
};
48+
49+
export const useAgentAvailability = (): BackendFeatureStatus => {
50+
const { isLoading: agentLoading, error: agentError } =
51+
useCheckAgentAvailabilityQuery();
52+
53+
const { isLoading: searchLoading, error: searchError } =
54+
useCheckSearchAvailabilityQuery();
55+
56+
if (agentLoading || searchLoading) {
57+
return {
58+
isAvailable: false,
59+
isLoading: true,
60+
};
61+
}
62+
63+
const agentNotFound = agentError ? isNotFoundError(agentError) : false;
64+
const searchNotFound = searchError ? isNotFoundError(searchError) : false;
65+
66+
const isAvailable = !agentNotFound && !searchNotFound;
67+
68+
return {
69+
isAvailable,
70+
isLoading: false,
71+
};
72+
};

0 commit comments

Comments
 (0)