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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IntlProvider } from 'react-intl';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Provider } from 'react-redux';
import { configureStore, type EnhancedStore } from '@reduxjs/toolkit';
import { FloatingRunButton } from '../index';
import { FloatingRunButton, getPublishedRunUrl } from '../index';
import * as LogicAppsShared from '@microsoft/logic-apps-shared';
import * as DesignerViewSelectors from '../../../core/state/designerView/designerViewSelectors';

Expand Down Expand Up @@ -370,6 +370,97 @@ describe('FloatingRunButton', () => {
});
});

describe('getPublishedRunUrl', () => {
it('should construct standard ARM /run URL with hostruntime path', () => {
const result = getPublishedRunUrl({
siteResourceId: '/subscriptions/123/resourceGroups/rg/providers/Microsoft.Web/sites/myApp',
workflowName: 'testWorkflow',
triggerId: 'manual',
isConsumption: false,
});

expect(result).toBe(
'/subscriptions/123/resourceGroups/rg/providers/Microsoft.Web/sites/myApp/hostruntime/runtime/webhooks/workflow/api/management/workflows/testWorkflow/triggers/manual/run'
);
});

it('should construct consumption ARM /run URL without hostruntime path', () => {
const result = getPublishedRunUrl({
siteResourceId: '/subscriptions/123/resourceGroups/rg/providers/Microsoft.Logic/workflows/myApp',
workflowName: 'myWorkflow',
triggerId: 'manual',
isConsumption: true,
});

expect(result).toBe('/subscriptions/123/resourceGroups/rg/providers/Microsoft.Logic/workflows/myApp/triggers/manual/run');
});

it('should not include workflowName in consumption URL', () => {
const result = getPublishedRunUrl({
siteResourceId: '/subscriptions/123/resourceGroups/rg/providers/Microsoft.Logic/workflows/myApp',
workflowName: 'shouldNotAppearInPath',
triggerId: 'manual',
isConsumption: true,
});

expect(result).not.toContain('shouldNotAppearInPath');
});

it('should include workflowName in standard URL', () => {
const result = getPublishedRunUrl({
siteResourceId: '/subscriptions/123/resourceGroups/rg/providers/Microsoft.Web/sites/myApp',
workflowName: 'myWorkflow',
triggerId: 'manual',
isConsumption: false,
});

expect(result).toContain('/workflows/myWorkflow/');
});

it('should produce ARM resource paths not SAS URLs', () => {
const standardResult = getPublishedRunUrl({
siteResourceId: '/subscriptions/123/resourceGroups/rg/providers/Microsoft.Web/sites/myApp',
workflowName: 'wf',
triggerId: 'manual',
isConsumption: false,
});

const consumptionResult = getPublishedRunUrl({
siteResourceId: '/subscriptions/123/resourceGroups/rg/providers/Microsoft.Logic/workflows/myApp',
workflowName: 'wf',
triggerId: 'manual',
isConsumption: true,
});

for (const url of [standardResult, consumptionResult]) {
expect(url).toMatch(/^\/subscriptions\//);
expect(url).toContain('/triggers/manual/run');
expect(url).not.toContain('listCallbackUrl');
expect(url).not.toContain('invoke');
expect(url).not.toContain('sig=');
}
});

it('should end with /run for both standard and consumption', () => {
const standardResult = getPublishedRunUrl({
siteResourceId: '/subscriptions/123/resourceGroups/rg/providers/Microsoft.Web/sites/myApp',
workflowName: 'wf',
triggerId: 'trigger1',
isConsumption: false,
});

const consumptionResult = getPublishedRunUrl({
siteResourceId: '/subscriptions/123/resourceGroups/rg/providers/Microsoft.Logic/workflows/myApp',
workflowName: 'wf',
triggerId: 'trigger1',
isConsumption: true,
});

expect(standardResult).toMatch(/\/triggers\/trigger1\/run$/);
expect(consumptionResult).toMatch(/\/triggers\/trigger1\/run$/);
});
});

// Separate describe block for URL generation unit tests
describe('FloatingRunButton URL Generation', () => {
it('should construct consumption URL format correctly', () => {
Expand Down
35 changes: 32 additions & 3 deletions libs/designer-v2/src/lib/ui/FloatingRunButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
isNullOrEmpty,
parseErrorMessage,
RunService,
WorkflowService,
} from '@microsoft/logic-apps-shared';
import { useDispatch, useSelector } from 'react-redux';
import {
Expand Down Expand Up @@ -52,6 +51,27 @@ export type PayloadData = {
body?: string;
};

/**
* Constructs the ARM /run URL for triggering a published workflow.
* Standard workflows use the hostruntime management API path.
* Consumption workflows use the direct trigger path.
*/
export const getPublishedRunUrl = ({
siteResourceId,
workflowName,
triggerId,
isConsumption,
}: {
siteResourceId: string;
workflowName: string;
triggerId: string;
isConsumption: boolean;
}): string => {
return isConsumption
? `${siteResourceId}/triggers/${triggerId}/run`
: `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/workflows/${workflowName}/triggers/${triggerId}/run`;
};

export interface FloatingRunButtonProps {
siteResourceId?: string;
workflowName?: string;
Expand Down Expand Up @@ -244,8 +264,17 @@ export const FloatingRunButton = ({
return;
}

const callbackInfo = await WorkflowService().getCallbackUrl(triggerId);
callbackInfo.method = payload ? payload?.method : saveResponse?.definition?.triggers?.[triggerId]?.inputs?.method || 'POST';
// Use the ARM /run endpoint directly (like V1) instead of listCallbackUrl + SAS invoke
const runUrl = getPublishedRunUrl({
siteResourceId: siteResourceId ?? '',
workflowName: workflowName ?? '',
triggerId,
isConsumption: isConsumption ?? false,
});
const callbackInfo = {
value: runUrl,
method: payload ? payload?.method : saveResponse?.definition?.triggers?.[triggerId]?.inputs?.method || 'POST',
};
Comment thread
rllyy97 marked this conversation as resolved.

// Wait 0.5 seconds, running too fast after saving causes 500 error
await new Promise((resolve) => setTimeout(resolve, 500));
Expand Down
Loading