Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Localize/lang/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,7 @@
"Q13J5V": "Create deployment model",
"Q1LEiE": "Previous",
"Q2X3qQ": "Actions need to be triggered by another node, e.g. at regular intervals with the Schedule node",
"Q2p4Zh": "An error occurred while generating keys. Error details: {error}",
"Q2p4Zh": "An error occurred while generating keys. Error details: {errorMessage}",
"Q4TUFX": "Discard",
"Q5Fh2R": "Required. The string to calculate UTF-16 length from.",
"Q5w4Do": "Add dynamic data or expressions by inserting a /",
Expand Down Expand Up @@ -1616,7 +1616,7 @@
"YxH2JT": "When a message is received",
"Yz9o1k": "Not connected.",
"Yzw97z": "Choose a connection to use for this MCP server",
"Z1p3Yh": "An error occurred while updating authentication settings. Error details: {error}",
"Z1p3Yh": "An error occurred while updating authentication settings. Error details: {errorMessage}",
"Z3Ak88": "Add optional prompts or questions for the agent. For better results, focus each item on a single specific prompt or question.",
"Z8BOCl": "No identities available",
"Z8tBFS": "The workflow assistant is designed only to provide help and doesn't support workflow creation or editing.",
Expand Down
4 changes: 2 additions & 2 deletions apps/Standalone/src/mcp/app/McpServer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ export const McpServer = () => {
theme: state.workflowLoader.theme,
}));

const appId = '/subscriptions/f34b22a3-2202-4fb1-b040-1332bd928c84/resourceGroups/TestACSRG/providers/Microsoft.Web/sites/pritimcpserver';
const appId = '/subscriptions/f34b22a3-2202-4fb1-b040-1332bd928c84/resourceGroups/TestACSRG/providers/Microsoft.Web/sites/prititemplates';
const resourceDetails = useMemo(() => {
const parser = new ArmParser(appId);
return {
subscriptionId: parser.subscriptionId || '',
resourceGroup: parser.resourceGroup || '',
logicAppName: parser.resourceName || '',
location: 'westus',
location: 'westus2',
};
}, []);

Expand Down
8 changes: 6 additions & 2 deletions libs/designer/src/lib/core/mcp/utils/__test__/server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ vi.mock('@microsoft/logic-apps-shared', () => ({
formatMessage: vi.fn(({ defaultMessage }) => defaultMessage),
})),
getPropertyValue: vi.fn(),
getObjectPropertyValue: vi.fn(),
isNullOrEmpty: vi.fn(),
ResourceService: vi.fn(() => ({
executeResourceAction: vi.fn(),
Expand All @@ -26,6 +27,7 @@ describe('server utils', () => {
let mockEquals: any;
let mockGetIntl: any;
let mockGetPropertyValue: any;
let mockGetObjectPropertyValue: any;
let mockIsNullOrEmpty: any;
let mockResourceService: any;
let mockExecuteResourceAction: any;
Expand All @@ -43,6 +45,7 @@ describe('server utils', () => {
mockEquals = shared.equals as any;
mockGetIntl = shared.getIntl as any;
mockGetPropertyValue = shared.getPropertyValue as any;
mockGetObjectPropertyValue = shared.getObjectPropertyValue as any;
mockIsNullOrEmpty = shared.isNullOrEmpty as any;
mockResourceService = shared.ResourceService as any;
mockGetHostConfig = queries.getHostConfig as any;
Expand Down Expand Up @@ -370,7 +373,7 @@ describe('server utils', () => {
mockExecuteResourceAction.mockRejectedValue(new Error(errorMessage));

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

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

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

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

Expand Down
42 changes: 23 additions & 19 deletions libs/designer/src/lib/core/mcp/utils/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { equals, getIntl, getPropertyValue, isNullOrEmpty, ResourceService } from '@microsoft/logic-apps-shared';
import { equals, getIntl, getObjectPropertyValue, getPropertyValue, isNullOrEmpty, ResourceService } from '@microsoft/logic-apps-shared';
import { getHostConfig, resetQueriesOnServerAuthUpdate } from './queries';

export const validateMcpServerName = (serverName: string): string | undefined => {
Expand Down Expand Up @@ -86,16 +86,18 @@ export const updateAuthSettings = async (siteResourceId: string, options: string
{ 'api-version': '2020-06-01' },
{ files: { 'host.json': updatedConfig } }
);
} catch (error) {
const errorMessage = getIntl().formatMessage(
{
defaultMessage: 'An error occurred while updating authentication settings. Error details: {error}',
id: 'Z1p3Yh',
description: 'General error message for authentication settings update failure',
},
{ error: (error as any).message }
} catch (error: any) {
const errorMessage = error?.error?.message ?? error?.message ?? undefined;
throw new Error(
getIntl().formatMessage(
{
defaultMessage: 'An error occurred while updating authentication settings. Error details: {errorMessage}',
id: 'Z1p3Yh',
description: 'General error message for authentication settings update failure',
},
{ errorMessage }
)
);
throw new Error(errorMessage);
} finally {
resetQueriesOnServerAuthUpdate(siteResourceId);
}
Expand All @@ -112,16 +114,18 @@ export const generateKeys = async (siteResourceId: string, duration: string, acc
);

return getPropertyValue(response.headers, 'X-API-Key') as string;
} catch (error) {
const errorMessage = getIntl().formatMessage(
{
defaultMessage: 'An error occurred while generating keys. Error details: {error}',
id: 'Q2p4Zh',
description: 'General error message for key generation failure',
},
{ error: (error as any).message }
} catch (error: any) {
const errorMessage = getObjectPropertyValue(error, ['error', 'message']) ?? getObjectPropertyValue(error, ['message']) ?? undefined;
throw new Error(
getIntl().formatMessage(
{
defaultMessage: 'An error occurred while generating keys. Error details: {errorMessage}',
id: 'Q2p4Zh',
description: 'General error message for key generation failure',
},
{ errorMessage }
)
);
throw new Error(errorMessage);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IntlProvider } from 'react-intl';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import userEvent from '@testing-library/user-event';
import { GenerateKeys } from '../generatekeys';
import { InitResourceService } from '@microsoft/logic-apps-shared';

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

const setupService = (throwError?: boolean) => {
InitResourceService({
executeResourceAction: () =>
throwError ? Promise.reject(new Error('Test error')) : Promise.resolve({ headers: { 'X-API-Key': 'generated-test-key-12345' } }),
} as any);
};

const renderWithProviders = (initialState = {}) => {
mockStore = configureStore({
reducer: {
Expand Down Expand Up @@ -378,6 +386,30 @@ describe('GenerateKeys', () => {
expect(durationDropdown?.value).toBe('24 hours');
expect(accessKeyDropdown?.value).toBe('Primary key');
});

it('show success message when generateKeys is successful', async () => {
setupService();
renderWithProviders();

fireEvent.click(screen.getByText('Generate'));

await waitFor(() => {
expect(screen.getByTestId('message-bar')).toBeTruthy();
expect(screen.getByText('Successfully generated the key.')).toBeTruthy();
});
});

it('show error message when generateKeys fails', async () => {
setupService(/* throwError */ true);
renderWithProviders();

fireEvent.click(screen.getByText('Generate'));

await waitFor(() => {
expect(screen.getByTestId('message-bar-body')).toBeTruthy();
expect(screen.getByText(/An error occurred while generating keys./)).toBeTruthy();
});
});
});

describe('Button States', () => {
Expand Down
4 changes: 2 additions & 2 deletions libs/designer/src/lib/ui/mcp/panel/server/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ export const CreateServer = ({
description={INTL_TEXT.detailsDescription}
descriptionLink={{
text: INTL_TEXT.learnMoreLinkText,
href: 'https://learn.microsoft.com/en-us/azure/logic-apps/microsoft-cloud-platform/mcp-overview',
href: 'https://go.microsoft.com/fwlink/?linkid=2350302',
}}
items={serverSectionItems}
/>
Expand All @@ -358,7 +358,7 @@ export const CreateServer = ({
description={INTL_TEXT.workflowsDescription}
descriptionLink={{
text: INTL_TEXT.learnMoreLinkText,
href: 'https://learn.microsoft.com/en-us/azure/logic-apps/microsoft-cloud-platform/mcp-overview',
href: 'https://go.microsoft.com/fwlink/?linkid=2350400',
}}
items={workflowSectionItems}
/>
Expand Down
26 changes: 21 additions & 5 deletions libs/designer/src/lib/ui/mcp/panel/server/generatekeys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export const GenerateKeys = () => {
const [expiresTime, setExpiresTime] = useState<string | undefined>(undefined);
const [showSuccessInfo, setShowSuccessInfo] = useState<boolean>(false);
const [dateTimeError, setDateTimeError] = useState<string | undefined>(undefined);
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);

const onDurationSelect = useCallback((options: string[]) => {
setDuration(options[0]);
Expand Down Expand Up @@ -282,10 +283,16 @@ export const GenerateKeys = () => {
: duration.endsWith('d')
? addExpiryToCurrent(/* hours */ undefined, Number.parseInt(duration.replace('d', '')))
: 'noexpiry';
const key = await generateKeys(logicAppId, expiryTime, accessKey);
setGeneratedKey(key);
setExpiresTime(expiryTime === 'noexpiry' ? INTL_TEXT.neverExpiresText : expiryTime);
setShowSuccessInfo(true);

try {
const key = await generateKeys(logicAppId, expiryTime, accessKey);
setGeneratedKey(key);
setExpiresTime(expiryTime === 'noexpiry' ? INTL_TEXT.neverExpiresText : expiryTime);
setShowSuccessInfo(true);
setErrorMessage(undefined);
} catch (error: any) {
setErrorMessage(error.message);
}
}, [duration, customDateTime, logicAppId, accessKey, INTL_TEXT.neverExpiresText]);

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

const renderErrorBar = useCallback(() => {
return (
<MessageBar intent={'error'}>
<MessageBarBody className={styles.messageBarBody}>{errorMessage}</MessageBarBody>
</MessageBar>
);
}, [styles.messageBarBody, errorMessage]);

const footerContent: TemplatePanelFooterProps = useMemo(() => {
return {
buttonContents: [
Expand Down Expand Up @@ -353,7 +368,8 @@ export const GenerateKeys = () => {
}}
items={keySectionItems}
/>
{showSuccessInfo ? (
{errorMessage ? renderErrorBar() : null}
{showSuccessInfo && !errorMessage ? (
<TemplatesSection
cssOverrides={{ sectionItems: styles.workflowSection }}
title={INTL_TEXT.resultTitle}
Expand Down
Loading