Skip to content

Commit a3edd5d

Browse files
Eric-B-Wusiddharth-mshartra344
authored
feat(designer): Hybrid Work on Designer (#5033)
* Migrating APIs to hybrid logic apps * Push for preview * Dynamic Invoke * Run History * Fix App Setting and Deploy Artifact * change version to allow for testing * chore(release): 0.4.0 * Update hybrid app checks to be resiliant * chore(release): 0.4.1 * test stuff * chore(release): 0.4.2 * test stuff * chore(release): 0.4.3 * temp commit * remove console.log * remove unnecessary changes --------- Co-authored-by: Siddharth <[email protected]> Co-authored-by: Travis Harris <[email protected]> Co-authored-by: [email protected] <hartra344>
1 parent 68cdd46 commit a3edd5d

File tree

21 files changed

+425
-92
lines changed

21 files changed

+425
-92
lines changed

apps/Standalone/src/designer/app/AzureLogicAppsDesigner/LogicAppSelectionSetting/AzureStandardLogicAppSelector.tsx

+35-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import { environment } from '../../../../environments/environment';
22
import type { AppDispatch } from '../../../state/store';
3-
import { useAppId, useIsMonitoringView, useRunId, useWorkflowName } from '../../../state/workflowLoadingSelectors';
3+
import { useAppId, useIsMonitoringView, useRunId, useWorkflowName, useHostingPlan } from '../../../state/workflowLoadingSelectors';
44
import { setAppid, setResourcePath, changeRunId, setWorkflowName } from '../../../state/workflowLoadingSlice';
55
import type { WorkflowList, RunList } from '../Models/WorkflowListTypes';
66
import { useFetchStandardApps } from '../Queries/FetchStandardApps';
7+
import { useFetchHybridApps } from '../Queries/FetchHybridApps';
78
import type { IComboBoxOption, IDropdownOption, IStackProps, IComboBoxStyles } from '@fluentui/react';
89
import { ComboBox, Dropdown, Spinner, Stack } from '@fluentui/react';
910
import axios from 'axios';
1011
import { useEffect } from 'react';
1112
import { useQuery } from '@tanstack/react-query';
1213
import { useDispatch } from 'react-redux';
14+
import { HybridAppUtility } from '../Utilities/HybridAppUtilities';
1315

1416
const columnProps: Partial<IStackProps> = {
1517
tokens: { childrenGap: 15 },
@@ -25,15 +27,24 @@ export const AzureStandardLogicAppSelector = () => {
2527
const workflowName = useWorkflowName();
2628
const runId = useRunId();
2729
const isMonitoringView = useIsMonitoringView();
28-
const { data: appList, isLoading: isAppsLoading } = useFetchStandardApps();
30+
const hostingPlan = useHostingPlan();
31+
const standardList = useFetchStandardApps();
32+
const hybridList = useFetchHybridApps();
33+
const { data: appList, isLoading: isAppsLoading } = hostingPlan === 'hybrid' ? hybridList : standardList;
2934
const validApp = appId ? resourceIdValidation.test(appId) : false;
3035
const dispatch = useDispatch<AppDispatch>();
3136

3237
const { data: workflows, isLoading: isWorkflowsLoading } = useQuery(['getListOfWorkflows', appId], async () => {
3338
if (!validApp) {
3439
return null;
3540
}
36-
const results = await axios.get<WorkflowList>(`https://management.azure.com${appId}/workflows?api-version=2018-11-01`, {
41+
42+
const getWorkflowUrl =
43+
hostingPlan === 'hybrid'
44+
? `https://management.azure.com${HybridAppUtility.getHybridAppBaseRelativeUrl(appId)}/workflows?api-version=2024-02-02-preview`
45+
: `https://management.azure.com${appId}/workflows?api-version=2018-11-01`;
46+
47+
const results = await axios.get<WorkflowList>(getWorkflowUrl, {
3748
headers: {
3849
Authorization: `Bearer ${environment.armToken}`,
3950
},
@@ -51,11 +62,24 @@ export const AzureStandardLogicAppSelector = () => {
5162
if (!validApp) {
5263
return null;
5364
}
65+
66+
const authToken = {
67+
Authorization: `Bearer ${environment.armToken}`,
68+
};
69+
70+
if (hostingPlan === 'hybrid') {
71+
return HybridAppUtility.getProxy<RunList>(
72+
`https://management.azure.com${appId}/hostruntime/runtime/webhooks/workflow/api/management/workflows/${workflowName}/runs`,
73+
null,
74+
authToken
75+
);
76+
}
77+
5478
const results = await axios.get<RunList>(
5579
`https://management.azure.com${appId}/hostruntime/runtime/webhooks/workflow/api/management/workflows/${workflowName}/runs?api-version=2018-11-01`,
5680
{
5781
headers: {
58-
Authorization: `Bearer ${environment.armToken}`,
82+
...authToken,
5983
},
6084
}
6185
);
@@ -139,6 +163,13 @@ export const AzureStandardLogicAppSelector = () => {
139163
disabled={workflowOptions.length === 0 || !appId || isWorkflowsLoading}
140164
defaultValue={workflowName}
141165
onChange={(_, option) => {
166+
dispatch(
167+
setResourcePath(
168+
hostingPlan === 'hybrid'
169+
? `${HybridAppUtility.getHybridAppBaseRelativeUrl(appId)}/workflows/${option?.key}`
170+
: `${appId}/workflows/${option?.key}`
171+
)
172+
);
142173
dispatch(setWorkflowName(option?.key as string));
143174
dispatch(setResourcePath(`${appId}/workflows/${option?.key}`));
144175
}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { environment } from '../../../../environments/environment';
2+
import type { Data as FetchLogicAppsData } from '../Models/LogicAppAppTypes';
3+
import { fetchAppsByQuery } from '../Utilities/resourceUtilities';
4+
import { useQuery } from '@tanstack/react-query';
5+
6+
export const useFetchHybridApps = () => {
7+
return useQuery<FetchLogicAppsData[]>(
8+
['listAllLogicApps', 'hybrid'],
9+
async () => {
10+
if (!environment.armToken) {
11+
return [];
12+
}
13+
const query = `resources | where type =~ "microsoft.app/containerApps"`;
14+
const data = await fetchAppsByQuery(query);
15+
return data.map((item: any) => ({
16+
id: item[0],
17+
name: item[1],
18+
location: item[5],
19+
resourceGroup: item[6],
20+
subscriptionId: item[7],
21+
properties: item[11],
22+
}));
23+
},
24+
{
25+
refetchOnMount: false,
26+
refetchOnReconnect: false,
27+
refetchOnWindowFocus: false,
28+
}
29+
);
30+
};

apps/Standalone/src/designer/app/AzureLogicAppsDesigner/Services/WorkflowAndArtifacts.tsx

+119-40
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ import { useQuery } from '@tanstack/react-query';
1212
import { isSuccessResponse } from './HttpClient';
1313
import { fetchFileData, fetchFilesFromFolder } from './vfsService';
1414
import type { CustomCodeFileNameMapping } from '@microsoft/logic-apps-designer';
15+
import { HybridAppUtility } from '../Utilities/HybridAppUtilities';
16+
import type { HostingPlanTypes } from '../../../state/workflowLoadingSlice';
1517

1618
const baseUrl = 'https://management.azure.com';
1719
const standardApiVersion = '2020-06-01';
20+
const hybridApiVersion = '2024-02-02-preview';
1821
const consumptionApiVersion = '2019-05-01';
1922

2023
export const useConnectionsData = (appId?: string) => {
@@ -52,10 +55,13 @@ export const useWorkflowAndArtifactsStandard = (workflowId: string) => {
5255
['workflowArtifactsStandard', workflowId],
5356
async () => {
5457
const artifacts = [Artifact.ConnectionsFile, Artifact.ParametersFile];
55-
const uri = `${baseUrl}${validateResourceId(workflowId)}?api-version=${standardApiVersion}&$expand=${artifacts.join(',')}`;
58+
const uri = `${baseUrl}${validateResourceId(workflowId)}?api-version=${
59+
HybridAppUtility.isHybridLogicApp(workflowId) ? hybridApiVersion : standardApiVersion
60+
}&$expand=${artifacts.join(',')}`;
5661
const response = await axios.get(uri, {
5762
headers: {
5863
Authorization: `Bearer ${environment.armToken}`,
64+
'if-match': '*',
5965
},
6066
});
6167

@@ -69,13 +75,17 @@ export const useWorkflowAndArtifactsStandard = (workflowId: string) => {
6975
);
7076
};
7177

72-
export const useAllCustomCodeFiles = (appId?: string, workflowName?: string) => {
73-
return useQuery(['workflowCustomCode', appId, workflowName], async () => await getAllCustomCodeFiles(appId, workflowName), {
74-
enabled: !!appId && !!workflowName,
75-
refetchOnMount: false,
76-
refetchOnReconnect: false,
77-
refetchOnWindowFocus: false,
78-
});
78+
export const useAllCustomCodeFiles = (appId?: string, workflowName?: string, isHybridLogicApp?: boolean) => {
79+
return useQuery(
80+
['workflowCustomCode', appId, workflowName],
81+
async () => await getAllCustomCodeFiles(appId, workflowName, isHybridLogicApp),
82+
{
83+
enabled: !!appId && !!workflowName,
84+
refetchOnMount: false,
85+
refetchOnReconnect: false,
86+
refetchOnWindowFocus: false,
87+
}
88+
);
7989
};
8090

8191
interface HostJSON {
@@ -127,7 +137,14 @@ export const getCustomCodeAppFiles = async (
127137
return appFiles;
128138
};
129139

130-
const getAllCustomCodeFiles = async (appId?: string, workflowName?: string): Promise<Record<string, string>> => {
140+
const getAllCustomCodeFiles = async (
141+
appId?: string,
142+
workflowName?: string,
143+
isHybridLogicApp?: boolean
144+
): Promise<Record<string, string>> => {
145+
if (isHybridLogicApp) {
146+
return {};
147+
}
131148
const customCodeFiles: Record<string, string> = {};
132149
const uri = `${baseUrl}${appId}/hostruntime/admin/vfs/${workflowName}`;
133150
const vfsObjects: VFSObject[] = (await fetchFilesFromFolder(uri)).filter((file) => file.name !== Artifact.WorkflowFile);
@@ -173,6 +190,19 @@ export const useRunInstanceStandard = (
173190
return useQuery(
174191
['getRunInstance', appId, workflowName, runId],
175192
async () => {
193+
if (!appId) {
194+
return;
195+
}
196+
if (HybridAppUtility.isHybridLogicApp(appId)) {
197+
return HybridAppUtility.getProxy<LogicAppsV2.RunInstanceDefinition>(
198+
`${baseUrl}${appId}/hostruntime/runtime/webhooks/workflow/api/management/workflows/${workflowName}/runs/${runId}?api-version=2018-11-01&$expand=properties/actions,workflow/properties`,
199+
null,
200+
{
201+
Authorization: `Bearer ${environment.armToken}`,
202+
}
203+
);
204+
}
205+
176206
const results = await axios.get<LogicAppsV2.RunInstanceDefinition>(
177207
`${baseUrl}${appId}/hostruntime/runtime/webhooks/workflow/api/management/workflows/${workflowName}/runs/${runId}?api-version=2018-11-01&$expand=properties/actions,workflow/properties`,
178208
{
@@ -229,16 +259,23 @@ export const listCallbackUrl = async (
229259
): Promise<CallbackInfo> => {
230260
let callbackInfo: any;
231261
if (triggerName) {
232-
const result = await axios.post(
233-
`${baseUrl}${workflowId}/triggers/${triggerName}/listCallbackUrl?api-version=${isConsumption ? '2016-10-01' : standardApiVersion}`,
234-
null,
235-
{
236-
headers: {
237-
Authorization: `Bearer ${environment.armToken}`,
238-
},
239-
}
240-
);
241-
callbackInfo = result.data;
262+
const authToken = {
263+
Authorization: `Bearer ${environment.armToken}`,
264+
};
265+
if (HybridAppUtility.isHybridLogicApp(workflowId)) {
266+
callbackInfo = HybridAppUtility.postProxy(`${baseUrl}${workflowId}/triggers/${triggerName}/listCallbackUrl`, null, authToken);
267+
} else {
268+
const result = await axios.post(
269+
`${baseUrl}${workflowId}/triggers/${triggerName}/listCallbackUrl?api-version=${isConsumption ? '2016-10-01' : standardApiVersion}`,
270+
null,
271+
{
272+
headers: {
273+
...authToken,
274+
},
275+
}
276+
);
277+
callbackInfo = result.data;
278+
}
242279
} else {
243280
callbackInfo = {
244281
basePath: '',
@@ -262,11 +299,16 @@ export const listCallbackUrl = async (
262299
};
263300
};
264301

265-
export const useWorkflowApp = (siteResourceId: string, isConsumption = false) => {
302+
export const useWorkflowApp = (siteResourceId: string, hostingPlan: HostingPlanTypes) => {
266303
return useQuery(
267304
['workflowApp', siteResourceId],
268305
async () => {
269-
const uri = `${baseUrl}${siteResourceId}?api-version=${isConsumption ? '2016-10-01' : '2018-11-01'}`;
306+
const apiVersions = {
307+
consumption: '2016-10-01',
308+
standard: '2018-11-01',
309+
hybrid: '2023-11-02-preview',
310+
};
311+
const uri = `${baseUrl}${siteResourceId}?api-version=${apiVersions[hostingPlan] || '2018-11-01'}`;
270312
const response = await axios.get(uri, {
271313
headers: {
272314
Authorization: `Bearer ${environment.armToken}`,
@@ -287,14 +329,30 @@ export const useAppSettings = (siteResourceId: string) => {
287329
return useQuery(
288330
['appSettings', siteResourceId],
289331
async () => {
290-
const uri = `${baseUrl}${siteResourceId}/config/appsettings/list?api-version=2018-11-01`;
291-
const response = await axios.post(uri, null, {
292-
headers: {
293-
Authorization: `Bearer ${environment.armToken}`,
294-
},
295-
});
332+
if (HybridAppUtility.isHybridLogicApp(siteResourceId)) {
333+
const containerAppInfo = (
334+
await axios.get(`${baseUrl}${siteResourceId}?api-version=2024-02-02-preview`, {
335+
headers: {
336+
Authorization: `Bearer ${environment.armToken}`,
337+
},
338+
})
339+
).data;
340+
containerAppInfo.properties = containerAppInfo.properties.template.containers[0].env;
341+
containerAppInfo.properties = containerAppInfo.properties.reduce((acc: any, cur: any) => {
342+
acc[cur.name] = cur.value;
343+
return acc;
344+
}, {});
345+
return containerAppInfo;
346+
}
296347

297-
return response.data;
348+
const uri = `${baseUrl}${siteResourceId}/config/appsettings/list?api-version=2018-11-01`;
349+
return (
350+
await axios.post(uri, null, {
351+
headers: {
352+
Authorization: `Bearer ${environment.armToken}`,
353+
},
354+
})
355+
).data;
298356
},
299357
{
300358
refetchOnMount: false,
@@ -440,13 +498,23 @@ export const saveWorkflowStandard = async (
440498
// the host to go soft restart. We may need to look into if there's a race case where this may still happen
441499
// eventually we want to move this logic to the backend to happen with deployWorkflowArtifacts
442500
saveCustomCodeStandard(customCodeData);
443-
const response = await axios.post(`${baseUrl}${siteResourceId}/deployWorkflowArtifacts?api-version=${standardApiVersion}`, data, {
501+
502+
let url = null;
503+
if (HybridAppUtility.isHybridLogicApp(siteResourceId)) {
504+
url = `${baseUrl}${HybridAppUtility.getHybridAppBaseRelativeUrl(
505+
siteResourceId
506+
)}/deployWorkflowArtifacts?api-version=${hybridApiVersion}`;
507+
} else {
508+
url = `${baseUrl}${siteResourceId}/deployWorkflowArtifacts?api-version=${standardApiVersion}`;
509+
}
510+
const response = await axios.post(url, data, {
444511
headers: {
445512
'If-Match': '*',
446513
'Content-Type': 'application/json',
447514
Authorization: `Bearer ${environment.armToken}`,
448515
},
449516
});
517+
450518
if (!isSuccessResponse(response.status)) {
451519
alert('Failed to save workflow');
452520
return;
@@ -504,18 +572,29 @@ const validateWorkflow = async (
504572
requestPayload.properties.appsettings = { Values: settings };
505573
}
506574

507-
const response = await axios.post(
508-
`${baseUrl}${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/workflows/${workflowName}/validate?api-version=${
509-
isConsumption ? consumptionApiVersion : standardApiVersion
510-
}`,
511-
requestPayload,
512-
{
513-
headers: {
514-
'Content-Type': 'application/json',
575+
let response = null;
576+
if (HybridAppUtility.isHybridLogicApp(siteResourceId)) {
577+
response = await HybridAppUtility.postProxyResponse(
578+
`${baseUrl}${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/workflows/${workflowName}/validate`,
579+
requestPayload,
580+
{
515581
Authorization: `Bearer ${environment.armToken}`,
516-
},
517-
}
518-
);
582+
}
583+
);
584+
} else {
585+
response = await axios.post(
586+
`${baseUrl}${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/workflows/${workflowName}/validate?api-version=${
587+
isConsumption ? consumptionApiVersion : standardApiVersion
588+
}`,
589+
requestPayload,
590+
{
591+
headers: {
592+
'Content-Type': 'application/json',
593+
Authorization: `Bearer ${environment.armToken}`,
594+
},
595+
}
596+
);
597+
}
519598

520599
if (response.status !== 200) {
521600
return Promise.reject(response);

0 commit comments

Comments
 (0)