Skip to content

Commit

Permalink
feat(Templates): End to End Create Experience With Sanitized Paramete…
Browse files Browse the repository at this point in the history
…rs (#4974)

* added logic back with sanitizing parameters

* fixed fetching parameters call

* fixed parameter replace to replaceAll

* updated submodule reference

* removed allowedValues for saving parameters

* made theme to be part of other useSelector
  • Loading branch information
Elaina-Lee committed Jun 13, 2024
1 parent 6d93273 commit c8c94ce
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 144 deletions.
182 changes: 115 additions & 67 deletions apps/Standalone/src/templates/app/TemplatesStandaloneDesigner.tsx
Original file line number Diff line number Diff line change
@@ -1,91 +1,139 @@
import type { ReactNode } from 'react';
import { ReactQueryProvider, TemplatesDataProvider } from '@microsoft/logic-apps-designer';
import { loadToken } from '../../environments/environment';
import { environment, loadToken } from '../../environments/environment';
import { DevToolbox } from '../components/DevToolbox';
import type { RootState } from '../state/Store';
import { TemplatesDesigner, TemplatesDesignerProvider } from '@microsoft/logic-apps-designer';
import { useQuery } from '@tanstack/react-query';
import { useSelector } from 'react-redux';
// import { useNavigate } from 'react-router-dom';
// import type { Template, LogicAppsV2 } from '@microsoft/logic-apps-shared';
// import { saveWorkflowStandard } from '../../designer/app/AzureLogicAppsDesigner/Services/WorkflowAndArtifacts';
// import type { ParametersData } from '../../designer/app/AzureLogicAppsDesigner/Models/Workflow';
import { useNavigate } from 'react-router-dom';
import type { Template, LogicAppsV2 } from '@microsoft/logic-apps-shared';
import { saveWorkflowStandard } from '../../designer/app/AzureLogicAppsDesigner/Services/WorkflowAndArtifacts';
import type { ParametersData } from '../../designer/app/AzureLogicAppsDesigner/Models/Workflow';
import axios from 'axios';

const LoadWhenArmTokenIsLoaded = ({ children }: { children: ReactNode }) => {
const { isLoading } = useQuery(['armToken'], loadToken);
return isLoading ? null : <>{children}</>;
};
export const TemplatesStandaloneDesigner = () => {
const theme = useSelector((state: RootState) => state.workflowLoader.theme);
const {
// appId,
isConsumption,
workflowName: existingWorkflowName,
} = useSelector((state: RootState) => state.workflowLoader);
// const navigate = useNavigate();
const { appId, isConsumption, workflowName: existingWorkflowName, theme } = useSelector((state: RootState) => state.workflowLoader);
const navigate = useNavigate();

// const createWorkflowCall = async (
// workflowName: string,
// workflowKind: string,
// workflowDefinition: LogicAppsV2.WorkflowDefinition,
// _connectionsData: any,
// parametersData: Record<string, Template.ParameterDefinition>
// ) => {
// const workflowNameToUse = existingWorTemplatesDesignerkflowName ?? workflowName;
// const workflow = {
// definition: workflowDefinition,
// connectionReferences: undefined, //TODO: change this after connections is done
// parameters: parametersData,
// kind: workflowKind,
// };
// const callBack = () => {
// console.log('Created workflow, TODO: now redirect');
// navigate('/');
// };
// if (appId) {
// if (isConsumption) {
// console.log('Consumption is not ready yet!');
// // await saveWorkflowConsumption({
// // id: appId,
// // name: workflowNameToUse,
// // type: "json", //TODO: figure out what this type is and replace it
// // kind: workflowKind,
// // properties: {
// // files: {
// // [Artifact.WorkflowFile]: workflow,
// // [Artifact.ParametersFile]: parametersData as ParametersData,
// // [Artifact.ConnectionsFile]: _connectionsData
// // },
// // health: {},
// // }
// // }, workflow);
// } else {
// await saveWorkflowStandard(
// appId,
// workflowNameToUse,
// workflow,
// undefined,
// parametersData as ParametersData,
// undefined,
// undefined,
// callBack,
// true
// );
// }
// } else {
// console.log('Select App Id first!');
// }
// };
const sanitizeParameterName = (parameterName: string, workflowName: string) =>
parameterName.replace('_#workflowname#', `_${workflowName}`);

const createWorkflowCall = async (
workflowName: string,
workflowKind: string,
workflowDefinition: LogicAppsV2.WorkflowDefinition,
_connectionsData: any,
parametersData: Record<string, Template.ParameterDefinition>
) => {
const workflowNameToUse = existingWorkflowName ?? workflowName;
const callBack = () => {
console.log('Created workflow, TODO: now redirect');
navigate('/');
};
if (appId) {
if (isConsumption) {
console.log('Consumption is not ready yet!');
// await saveWorkflowConsumption({
// id: appId,
// name: workflowNameToUse,
// type: "json", //TODO: figure out what this type is and replace it
// kind: workflowKind,
// properties: {
// files: {
// [Artifact.WorkflowFile]: workflow,
// [Artifact.ParametersFile]: parametersData as ParametersData,
// [Artifact.ConnectionsFile]: _connectionsData
// },
// health: {},
// }
// }, workflow);
} else {
let sanitizedWorkflowDefinitionString = JSON.stringify(workflowDefinition);
const sanitizedParameterData: ParametersData = {};

// Sanitizing parameter name & body
Object.keys(parametersData).forEach((key) => {
const parameter = parametersData[key];
const sanitizedParameterName = sanitizeParameterName(parameter.name, workflowName);
sanitizedParameterData[sanitizedParameterName] = {
type: parameter.type,
description: parameter?.description,
value: parameter?.value ?? parameter?.default,
};
sanitizedWorkflowDefinitionString = sanitizedWorkflowDefinitionString.replaceAll(
`@parameters('${parameter.name}')`,
`@parameters('${sanitizedParameterName}')`
);
});

const workflow = {
definition: JSON.parse(sanitizedWorkflowDefinitionString),
connectionReferences: undefined, //TODO: change this after connections is done
parameters: sanitizedParameterData,
kind: workflowKind,
};

const getExistingParametersData = async () => {
try {
const response = await axios.get(
`https://management.azure.com${appId}/hostruntime/admin/vfs/parameters.json?api-version=2018-11-01&relativepath=1`,
{
headers: {
'If-Match': '*',
'Content-Type': 'application/json',
Authorization: `Bearer ${environment.armToken}`,
},
}
);
return response.data as ParametersData;
} catch (error: any) {
return error?.response?.status === 404 ? {} : undefined;
}
};
try {
const existingParametersData = await getExistingParametersData();

if (!existingParametersData) {
alert('Error fetching parameters');
return;
}

const updatedParametersData: ParametersData = {
...existingParametersData,
...sanitizedParameterData,
};
await saveWorkflowStandard(
appId,
workflowNameToUse,
workflow,
undefined,
updatedParametersData,
undefined,
undefined,
callBack,
true
);
} catch (error) {
console.log(error);
}
}
} else {
console.log('Select App Id first!');
}
};

return (
<ReactQueryProvider>
<LoadWhenArmTokenIsLoaded>
<DevToolbox />
<TemplatesDesignerProvider locale="en-US" theme={theme}>
<TemplatesDataProvider isConsumption={isConsumption} existingWorkflowName={existingWorkflowName}>
<TemplatesDesigner
// createWorkflowCall={createWorkflowCall}
/>
<TemplatesDesigner createWorkflowCall={createWorkflowCall} />
</TemplatesDataProvider>
</TemplatesDesignerProvider>
</LoadWhenArmTokenIsLoaded>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,10 @@ import type { SelectTabData } from '@fluentui/react-components';
import { selectPanelTab } from '../../../../core/state/templates/panelSlice';
import { usePanelTabs } from './usePanelTabs';

export const CreateWorkflowPanel = (
// { onCreateClick }: { onCreateClick: () => Promise<void> }
) => {
export const CreateWorkflowPanel = ({ onCreateClick }: { onCreateClick: () => Promise<void> }) => {
const dispatch = useDispatch<AppDispatch>();
const intl = useIntl();
const panelTabs =
usePanelTabs(
// onCreateClick
);
const panelTabs = usePanelTabs(onCreateClick);
const selectedTabId = useSelector((state: RootState) => state.panel.selectedTabId) ?? panelTabs[0]?.id;

const intlText = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import { Button, Spinner } from '@fluentui/react-components';
import constants from '../../../../../common/constants';
import { useState } from 'react';

export const ReviewCreatePanel = (
// { onCreateClick }: { onCreateClick: () => Promise<void> }
) => {
export const ReviewCreatePanel = ({ onCreateClick }: { onCreateClick: () => Promise<void> }) => {
const intl = useIntl();
const { workflowName, kind } = useSelector((state: RootState) => state.template);
const { existingWorkflowName } = useSelector((state: RootState) => state.workflow);
Expand All @@ -22,7 +20,7 @@ export const ReviewCreatePanel = (

async function handleCreateClick() {
setIsLoadingCreate(true);
// await onCreateClick();
await onCreateClick();
setIsLoadingCreate(false);
}

Expand All @@ -42,10 +40,7 @@ export const ReviewCreatePanel = (
);
};

export const reviewCreateTab = (
intl: IntlShape
// , onCreateClick: () => Promise<void>
) => ({
export const reviewCreateTab = (intl: IntlShape, onCreateClick: () => Promise<void>) => ({
id: constants.TEMPLATE_PANEL_TAB_NAMES.REVIEW_AND_CREATE,
title: intl.formatMessage({
defaultMessage: 'Review and Create',
Expand All @@ -58,11 +53,7 @@ export const reviewCreateTab = (
description: 'An accessability label that describes the review and create tab',
}),
visible: true,
content: (
<ReviewCreatePanel
// onCreateClick={onCreateClick}
/>
),
content: <ReviewCreatePanel onCreateClick={onCreateClick} />,
order: 3,
icon: 'Info',
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import { parametersTab } from './tabs/parametersTab';
import { nameStateTab } from './tabs/nameStateTab';
import { reviewCreateTab } from './tabs/reviewCreateTab';

export const usePanelTabs = (
// onCreateClick: () => Promise<void>
) => {
export const usePanelTabs = (onCreateClick: () => Promise<void>) => {
const intl = useIntl();

const connectionsTabItem = useMemo(
Expand All @@ -31,17 +29,11 @@ export const usePanelTabs = (
[intl]
);

// const reviewCreateTabItem = useMemo(
// () => ({
// ...reviewCreateTab(intl, onCreateClick),
// }),
// [intl, onCreateClick]
// );
const reviewCreateTabItem = useMemo(
() => ({
...reviewCreateTab(intl),
...reviewCreateTab(intl, onCreateClick),
}),
[intl]
[intl, onCreateClick]
);

return [connectionsTabItem, parametersTabItem, nameStateTabItem, reviewCreateTabItem];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import { closePanel } from '../../../core/state/templates/panelSlice';
import { CreateWorkflowPanel } from './createWorkflowPanel/createWorkflowPanel';
import { QuickViewPanel } from './quickViewPanel/quickViewPanel';

export const TemplatePanel = (
// { onCreateClick }: { onCreateClick: () => Promise<void> }
) => {
export const TemplatePanel = ({ onCreateClick }: { onCreateClick: () => Promise<void> }) => {
const dispatch = useDispatch<AppDispatch>();
const { isOpen, currentPanelView } = useSelector((state: RootState) => state.panel);

Expand All @@ -17,9 +15,7 @@ export const TemplatePanel = (
return (
<Panel isLightDismiss type={PanelType.medium} isOpen={isOpen} onDismiss={dismissPanel} hasCloseButton={true}>
{currentPanelView === 'createWorkflow' ? (
<CreateWorkflowPanel
// onCreateClick={onCreateClick}
/>
<CreateWorkflowPanel onCreateClick={onCreateClick} />
) : currentPanelView === 'quickView' ? (
<QuickViewPanel />
) : null}
Expand Down
Loading

0 comments on commit c8c94ce

Please sign in to comment.