Skip to content

Commit 9e4f94a

Browse files
preetriti1Priti Sambandam
andauthored
fix(mcp): Adding error handling for generate keys panel (#8795)
* fix(mcp): Adding error handling for generate keys panel * Removing temp change used for testing * Fixing test case --------- Co-authored-by: Priti Sambandam <psamband@microsoft.com>
1 parent 2b2b775 commit 9e4f94a

File tree

7 files changed

+88
-32
lines changed

7 files changed

+88
-32
lines changed

Localize/lang/strings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,7 @@
12051205
"Q13J5V": "Create deployment model",
12061206
"Q1LEiE": "Previous",
12071207
"Q2X3qQ": "Actions need to be triggered by another node, e.g. at regular intervals with the Schedule node",
1208-
"Q2p4Zh": "An error occurred while generating keys. Error details: {error}",
1208+
"Q2p4Zh": "An error occurred while generating keys. Error details: {errorMessage}",
12091209
"Q4TUFX": "Discard",
12101210
"Q5Fh2R": "Required. The string to calculate UTF-16 length from.",
12111211
"Q5w4Do": "Add dynamic data or expressions by inserting a /",
@@ -1616,7 +1616,7 @@
16161616
"YxH2JT": "When a message is received",
16171617
"Yz9o1k": "Not connected.",
16181618
"Yzw97z": "Choose a connection to use for this MCP server",
1619-
"Z1p3Yh": "An error occurred while updating authentication settings. Error details: {error}",
1619+
"Z1p3Yh": "An error occurred while updating authentication settings. Error details: {errorMessage}",
16201620
"Z3Ak88": "Add optional prompts or questions for the agent. For better results, focus each item on a single specific prompt or question.",
16211621
"Z8BOCl": "No identities available",
16221622
"Z8tBFS": "The workflow assistant is designed only to provide help and doesn't support workflow creation or editing.",

apps/Standalone/src/mcp/app/McpServer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ export const McpServer = () => {
1717
theme: state.workflowLoader.theme,
1818
}));
1919

20-
const appId = '/subscriptions/f34b22a3-2202-4fb1-b040-1332bd928c84/resourceGroups/TestACSRG/providers/Microsoft.Web/sites/pritimcpserver';
20+
const appId = '/subscriptions/f34b22a3-2202-4fb1-b040-1332bd928c84/resourceGroups/TestACSRG/providers/Microsoft.Web/sites/prititemplates';
2121
const resourceDetails = useMemo(() => {
2222
const parser = new ArmParser(appId);
2323
return {
2424
subscriptionId: parser.subscriptionId || '',
2525
resourceGroup: parser.resourceGroup || '',
2626
logicAppName: parser.resourceName || '',
27-
location: 'westus',
27+
location: 'westus2',
2828
};
2929
}, []);
3030

libs/designer/src/lib/core/mcp/utils/__test__/server.spec.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ vi.mock('@microsoft/logic-apps-shared', () => ({
1111
formatMessage: vi.fn(({ defaultMessage }) => defaultMessage),
1212
})),
1313
getPropertyValue: vi.fn(),
14+
getObjectPropertyValue: vi.fn(),
1415
isNullOrEmpty: vi.fn(),
1516
ResourceService: vi.fn(() => ({
1617
executeResourceAction: vi.fn(),
@@ -26,6 +27,7 @@ describe('server utils', () => {
2627
let mockEquals: any;
2728
let mockGetIntl: any;
2829
let mockGetPropertyValue: any;
30+
let mockGetObjectPropertyValue: any;
2931
let mockIsNullOrEmpty: any;
3032
let mockResourceService: any;
3133
let mockExecuteResourceAction: any;
@@ -43,6 +45,7 @@ describe('server utils', () => {
4345
mockEquals = shared.equals as any;
4446
mockGetIntl = shared.getIntl as any;
4547
mockGetPropertyValue = shared.getPropertyValue as any;
48+
mockGetObjectPropertyValue = shared.getObjectPropertyValue as any;
4649
mockIsNullOrEmpty = shared.isNullOrEmpty as any;
4750
mockResourceService = shared.ResourceService as any;
4851
mockGetHostConfig = queries.getHostConfig as any;
@@ -370,7 +373,7 @@ describe('server utils', () => {
370373
mockExecuteResourceAction.mockRejectedValue(new Error(errorMessage));
371374

372375
await expect(updateAuthSettings(siteResourceId, ['apikey'])).rejects.toThrow(
373-
`An error occurred while updating authentication settings. Error details: ${errorMessage}`
376+
/An error occurred while updating authentication settings./
374377
);
375378

376379
expect(mockResetQueriesOnServerAuthUpdate).toHaveBeenCalledWith(siteResourceId);
@@ -443,10 +446,11 @@ describe('server utils', () => {
443446

444447
it('should throw error when key generation fails', async () => {
445448
const errorMessage = 'Key generation failed';
449+
mockGetObjectPropertyValue.mockReturnValue(errorMessage);
446450
mockExecuteResourceAction.mockRejectedValue(new Error(errorMessage));
447451

448452
await expect(generateKeys(siteResourceId, '2024-12-31T23:59:59Z', 'primary')).rejects.toThrow(
449-
`An error occurred while generating keys. Error details: ${errorMessage}`
453+
/An error occurred while generating keys/
450454
);
451455
});
452456

libs/designer/src/lib/core/mcp/utils/server.ts

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { equals, getIntl, getPropertyValue, isNullOrEmpty, ResourceService } from '@microsoft/logic-apps-shared';
1+
import { equals, getIntl, getObjectPropertyValue, getPropertyValue, isNullOrEmpty, ResourceService } from '@microsoft/logic-apps-shared';
22
import { getHostConfig, resetQueriesOnServerAuthUpdate } from './queries';
33

44
export const validateMcpServerName = (serverName: string): string | undefined => {
@@ -86,16 +86,18 @@ export const updateAuthSettings = async (siteResourceId: string, options: string
8686
{ 'api-version': '2020-06-01' },
8787
{ files: { 'host.json': updatedConfig } }
8888
);
89-
} catch (error) {
90-
const errorMessage = getIntl().formatMessage(
91-
{
92-
defaultMessage: 'An error occurred while updating authentication settings. Error details: {error}',
93-
id: 'Z1p3Yh',
94-
description: 'General error message for authentication settings update failure',
95-
},
96-
{ error: (error as any).message }
89+
} catch (error: any) {
90+
const errorMessage = error?.error?.message ?? error?.message ?? undefined;
91+
throw new Error(
92+
getIntl().formatMessage(
93+
{
94+
defaultMessage: 'An error occurred while updating authentication settings. Error details: {errorMessage}',
95+
id: 'Z1p3Yh',
96+
description: 'General error message for authentication settings update failure',
97+
},
98+
{ errorMessage }
99+
)
97100
);
98-
throw new Error(errorMessage);
99101
} finally {
100102
resetQueriesOnServerAuthUpdate(siteResourceId);
101103
}
@@ -112,16 +114,18 @@ export const generateKeys = async (siteResourceId: string, duration: string, acc
112114
);
113115

114116
return getPropertyValue(response.headers, 'X-API-Key') as string;
115-
} catch (error) {
116-
const errorMessage = getIntl().formatMessage(
117-
{
118-
defaultMessage: 'An error occurred while generating keys. Error details: {error}',
119-
id: 'Q2p4Zh',
120-
description: 'General error message for key generation failure',
121-
},
122-
{ error: (error as any).message }
117+
} catch (error: any) {
118+
const errorMessage = getObjectPropertyValue(error, ['error', 'message']) ?? getObjectPropertyValue(error, ['message']) ?? undefined;
119+
throw new Error(
120+
getIntl().formatMessage(
121+
{
122+
defaultMessage: 'An error occurred while generating keys. Error details: {errorMessage}',
123+
id: 'Q2p4Zh',
124+
description: 'General error message for key generation failure',
125+
},
126+
{ errorMessage }
127+
)
123128
);
124-
throw new Error(errorMessage);
125129
}
126130
};
127131

libs/designer/src/lib/ui/mcp/panel/server/__test__/generatekeys.spec.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { IntlProvider } from 'react-intl';
1010
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
1111
import userEvent from '@testing-library/user-event';
1212
import { GenerateKeys } from '../generatekeys';
13+
import { InitResourceService } from '@microsoft/logic-apps-shared';
1314

1415
// Mock external dependencies
1516
vi.mock('../../../../core/state/mcp/panel/mcpPanelSlice', () => ({
@@ -240,6 +241,13 @@ describe('GenerateKeys', () => {
240241
let mockGenerateKeys: any;
241242
let mockGetStandardLogicAppId: any;
242243

244+
const setupService = (throwError?: boolean) => {
245+
InitResourceService({
246+
executeResourceAction: () =>
247+
throwError ? Promise.reject(new Error('Test error')) : Promise.resolve({ headers: { 'X-API-Key': 'generated-test-key-12345' } }),
248+
} as any);
249+
};
250+
243251
const renderWithProviders = (initialState = {}) => {
244252
mockStore = configureStore({
245253
reducer: {
@@ -378,6 +386,30 @@ describe('GenerateKeys', () => {
378386
expect(durationDropdown?.value).toBe('24 hours');
379387
expect(accessKeyDropdown?.value).toBe('Primary key');
380388
});
389+
390+
it('show success message when generateKeys is successful', async () => {
391+
setupService();
392+
renderWithProviders();
393+
394+
fireEvent.click(screen.getByText('Generate'));
395+
396+
await waitFor(() => {
397+
expect(screen.getByTestId('message-bar')).toBeTruthy();
398+
expect(screen.getByText('Successfully generated the key.')).toBeTruthy();
399+
});
400+
});
401+
402+
it('show error message when generateKeys fails', async () => {
403+
setupService(/* throwError */ true);
404+
renderWithProviders();
405+
406+
fireEvent.click(screen.getByText('Generate'));
407+
408+
await waitFor(() => {
409+
expect(screen.getByTestId('message-bar-body')).toBeTruthy();
410+
expect(screen.getByText(/An error occurred while generating keys./)).toBeTruthy();
411+
});
412+
});
381413
});
382414

383415
describe('Button States', () => {

libs/designer/src/lib/ui/mcp/panel/server/create.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ export const CreateServer = ({
348348
description={INTL_TEXT.detailsDescription}
349349
descriptionLink={{
350350
text: INTL_TEXT.learnMoreLinkText,
351-
href: 'https://learn.microsoft.com/en-us/azure/logic-apps/microsoft-cloud-platform/mcp-overview',
351+
href: 'https://go.microsoft.com/fwlink/?linkid=2350302',
352352
}}
353353
items={serverSectionItems}
354354
/>
@@ -358,7 +358,7 @@ export const CreateServer = ({
358358
description={INTL_TEXT.workflowsDescription}
359359
descriptionLink={{
360360
text: INTL_TEXT.learnMoreLinkText,
361-
href: 'https://learn.microsoft.com/en-us/azure/logic-apps/microsoft-cloud-platform/mcp-overview',
361+
href: 'https://go.microsoft.com/fwlink/?linkid=2350400',
362362
}}
363363
items={workflowSectionItems}
364364
/>

libs/designer/src/lib/ui/mcp/panel/server/generatekeys.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ export const GenerateKeys = () => {
190190
const [expiresTime, setExpiresTime] = useState<string | undefined>(undefined);
191191
const [showSuccessInfo, setShowSuccessInfo] = useState<boolean>(false);
192192
const [dateTimeError, setDateTimeError] = useState<string | undefined>(undefined);
193+
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
193194

194195
const onDurationSelect = useCallback((options: string[]) => {
195196
setDuration(options[0]);
@@ -282,10 +283,16 @@ export const GenerateKeys = () => {
282283
: duration.endsWith('d')
283284
? addExpiryToCurrent(/* hours */ undefined, Number.parseInt(duration.replace('d', '')))
284285
: 'noexpiry';
285-
const key = await generateKeys(logicAppId, expiryTime, accessKey);
286-
setGeneratedKey(key);
287-
setExpiresTime(expiryTime === 'noexpiry' ? INTL_TEXT.neverExpiresText : expiryTime);
288-
setShowSuccessInfo(true);
286+
287+
try {
288+
const key = await generateKeys(logicAppId, expiryTime, accessKey);
289+
setGeneratedKey(key);
290+
setExpiresTime(expiryTime === 'noexpiry' ? INTL_TEXT.neverExpiresText : expiryTime);
291+
setShowSuccessInfo(true);
292+
setErrorMessage(undefined);
293+
} catch (error: any) {
294+
setErrorMessage(error.message);
295+
}
289296
}, [duration, customDateTime, logicAppId, accessKey, INTL_TEXT.neverExpiresText]);
290297

291298
const handleClose = useCallback(() => {
@@ -305,6 +312,14 @@ export const GenerateKeys = () => {
305312
);
306313
}, [styles.messageBar, styles.messageBarBody, INTL_TEXT.infoTitle, INTL_TEXT.infoMessage]);
307314

315+
const renderErrorBar = useCallback(() => {
316+
return (
317+
<MessageBar intent={'error'}>
318+
<MessageBarBody className={styles.messageBarBody}>{errorMessage}</MessageBarBody>
319+
</MessageBar>
320+
);
321+
}, [styles.messageBarBody, errorMessage]);
322+
308323
const footerContent: TemplatePanelFooterProps = useMemo(() => {
309324
return {
310325
buttonContents: [
@@ -353,7 +368,8 @@ export const GenerateKeys = () => {
353368
}}
354369
items={keySectionItems}
355370
/>
356-
{showSuccessInfo ? (
371+
{errorMessage ? renderErrorBar() : null}
372+
{showSuccessInfo && !errorMessage ? (
357373
<TemplatesSection
358374
cssOverrides={{ sectionItems: styles.workflowSection }}
359375
title={INTL_TEXT.resultTitle}

0 commit comments

Comments
 (0)