Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(designer): Allow host to define conditions for built-in and custom connectors #4299

Merged
merged 16 commits into from
Mar 6, 2024
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
2 changes: 1 addition & 1 deletion libs/designer-ui/src/lib/authentication/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { MSIAuthentication } from './MSIAuth/MSIAuth';
import { RawAuthentication } from './RawAuth';
import { parseAuthEditor } from './util';
import type { IDropdownOption } from '@fluentui/react/lib/Dropdown';
import { getIntl, AssertionErrorCode, AssertionException, format } from '@microsoft/logic-apps-shared';
import { AssertionErrorCode, AssertionException, format, getIntl } from '@microsoft/logic-apps-shared';
import type { ManagedIdentity } from '@microsoft/logic-apps-shared';
import { useUpdateEffect } from '@react-hookz/web';
import { useState } from 'react';
Expand Down
70 changes: 70 additions & 0 deletions libs/designer-ui/src/lib/connectors/__test__/predicates.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as DesignerClientServices from '@microsoft/designer-client-services-logic-apps';
import type { Connector, OperationApi } from '@microsoft/logic-apps-shared';
import { isBuiltInConnector } from '../index';

const getMinimalHostService = (): DesignerClientServices.IHostService => ({
fetchAndDisplayContent: jest.fn(),
});

describe('lib/connectors', () => {
afterEach(() => {
jest.restoreAllMocks();
});

describe('isBuiltInConnector', () => {
describe('works with no host service callbacks using', () => {
test('string input', () => {
jest.spyOn(DesignerClientServices, 'HostService').mockImplementation(getMinimalHostService);

expect(isBuiltInConnector('/builtin/Terminate')).toBe(true);
expect(isBuiltInConnector('/subscriptions/special/builtin/format')).toBe(false);
});

test('Connector input', () => {
jest.spyOn(DesignerClientServices, 'HostService').mockImplementation(getMinimalHostService);

expect(isBuiltInConnector({ id: '/builtin/Terminate' } as Connector)).toBe(true);
expect(isBuiltInConnector({ id: '/subscriptions/special/builtin/format' } as Connector)).toBe(false);
});

test('OperationApi input', () => {
jest.spyOn(DesignerClientServices, 'HostService').mockImplementation(getMinimalHostService);

expect(isBuiltInConnector({ id: '/builtin/Terminate' } as OperationApi)).toBe(true);
expect(isBuiltInConnector({ id: '/subscriptions/special/builtin/format' } as OperationApi)).toBe(false);
});
});

describe('works with host service callback using', () => {
test('string input', () => {
jest.spyOn(DesignerClientServices, 'HostService').mockImplementation(() => ({
...getMinimalHostService(),
isBuiltInConnector: jest.fn().mockImplementation((value: string) => value === '/subscriptions/special/builtin/format'),
}));

expect(isBuiltInConnector('/builtin/Terminate')).toBe(false);
expect(isBuiltInConnector('/subscriptions/special/builtin/format')).toBe(true);
});

test('Connector input', () => {
jest.spyOn(DesignerClientServices, 'HostService').mockImplementation(() => ({
...getMinimalHostService(),
isBuiltInConnector: jest.fn().mockImplementation((value: Connector) => value.id === '/subscriptions/special/builtin/format'),
}));

expect(isBuiltInConnector({ id: '/builtin/Terminate' } as Connector)).toBe(false);
expect(isBuiltInConnector({ id: '/subscriptions/special/builtin/format' } as Connector)).toBe(true);
});

test('OperationApi input', () => {
jest.spyOn(DesignerClientServices, 'HostService').mockImplementation(() => ({
...getMinimalHostService(),
isBuiltInConnector: jest.fn().mockImplementation((value: OperationApi) => value.id === '/subscriptions/special/builtin/format'),
}));

expect(isBuiltInConnector({ id: '/builtin/Terminate' } as OperationApi)).toBe(false);
expect(isBuiltInConnector({ id: '/subscriptions/special/builtin/format' } as OperationApi)).toBe(true);
});
});
});
});
1 change: 1 addition & 0 deletions libs/designer-ui/src/lib/connectors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './predicates';
25 changes: 25 additions & 0 deletions libs/designer-ui/src/lib/connectors/predicates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { HostService } from '@microsoft/designer-client-services-logic-apps';
import type { Connector, OperationApi} from '@microsoft/logic-apps-shared';
import { getAllConnectorProperties, isBuiltInConnectorId, isCustomConnectorId, isString } from '@microsoft/logic-apps-shared';

export const isBuiltInConnector = (connector: Connector | OperationApi | string): boolean => {
const hostIsBuiltInConnectorFn = HostService()?.isBuiltInConnector;
if (hostIsBuiltInConnectorFn) {
const connectorParameter = isString(connector) ? connector : getAllConnectorProperties(connector);
return hostIsBuiltInConnectorFn(connectorParameter);
}

const connectorId = isString(connector) ? connector : connector.id;
return isBuiltInConnectorId(connectorId);
};

export const isCustomConnector = (connector: Connector | OperationApi | string): boolean => {
const hostIsCustomConnectorFn = HostService()?.isCustomConnector;
if (hostIsCustomConnectorFn) {
const connectorParameter = isString(connector) ? connector : getAllConnectorProperties(connector);
return hostIsCustomConnectorFn(connectorParameter);
}

const connectorId = isString(connector) ? connector : connector.id;
return isCustomConnectorId(connectorId);
};
24 changes: 13 additions & 11 deletions libs/designer-ui/src/lib/connectorsummarycard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
import { InfoDot } from '../infoDot';
import { Text, css } from '@fluentui/react';
import { fallbackConnectorIconUrl, isBuiltInConnector } from '@microsoft/logic-apps-shared';
import type { Connector, OperationApi } from '@microsoft/logic-apps-shared';
import { getDescriptionFromConnector, getDisplayNameFromConnector, getIconUriFromConnector } from '@microsoft/logic-apps-shared';
import { useCallback } from 'react';
import { isBuiltInConnector } from '../connectors/predicates';
import { InfoDot } from '../infoDot';

export interface ConnectorSummaryCardProps {
id: string;
connectorName: string;
connector: Connector | OperationApi;
displayRuntimeInfo: boolean;
description?: string;
iconUrl: string;
brandColor?: string;
category: string;
onClick?: (id: string) => void;
isCard?: boolean;
}

export const ConnectorSummaryCard = (props: ConnectorSummaryCardProps) => {
const { id, connectorName, description, iconUrl, category, onClick, isCard = true, displayRuntimeInfo } = props;
const { connector, category, onClick, isCard = true, displayRuntimeInfo } = props;
const { id } = connector;

const connectorName = getDisplayNameFromConnector(connector);
const description = getDescriptionFromConnector(connector);
const iconUrl = getIconUriFromConnector(connector);

const handleClick = () => onClick?.(id);

const ConnectorImage = useCallback(() => {
const src = fallbackConnectorIconUrl(iconUrl);
return <img className={css('msla-connector-summary-image', !isCard && 'large')} alt={connectorName} src={src} />;
return <img className={css('msla-connector-summary-image', !isCard && 'large')} alt={connectorName} src={iconUrl} />;
}, [connectorName, iconUrl, isCard]);

const isBuiltIn = isBuiltInConnector(id);
const isBuiltIn = isBuiltInConnector(connector);

const Content = () => (
<>
Expand Down
1 change: 1 addition & 0 deletions libs/designer-ui/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './checkbox';
// export * from './colorizer'
export * from './code';
export * from './combobox';
export * from './connectors';
export { default as UIConstants } from './constants';
export * from './html';
// export * from './conditioncontrol';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,14 @@ export const BrowseGrid = (props: BrowseGridProps) => {
const onRenderCell = useCallback(
(connector?: Connector, _index?: number) => {
if (!connector) return;
const { displayName, description, iconUri, brandColor, generalInformation } = connector.properties;
return (
<div className="mlsa-browse-list-tile-wrapper">
<div className="msla-browse-list-tile" style={{ width: forceSingleCol ? '100%' : '50%' }}>
<ConnectorSummaryCard
key={connector.id}
id={connector.id}
connectorName={displayName}
description={description || generalInformation?.description}
iconUrl={iconUri}
brandColor={brandColor}
connector={connector}
onClick={onConnectorSelected}
category={getConnectorCategoryString(connector.id)}
category={getConnectorCategoryString(connector)}
displayRuntimeInfo={displayRuntimeInfo}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ export interface OperationSearchGroupProps {

export const OperationSearchGroup = (props: OperationSearchGroupProps) => {
const { operationApi, operationActionsData, onConnectorClick, onOperationClick, displayRuntimeInfo } = props;
const { id, displayName, description, iconUri } = operationApi;
const { id } = operationApi;

const intl = useIntl();

const category = getConnectorCategoryString(id);
const category = getConnectorCategoryString(operationApi);

const seeMoreText = intl.formatMessage({
defaultMessage: 'See more',
Expand All @@ -29,15 +29,7 @@ export const OperationSearchGroup = (props: OperationSearchGroupProps) => {

return (
<div style={{ position: 'relative' }}>
<ConnectorSummaryCard
id={id}
connectorName={displayName}
description={description}
iconUrl={iconUri}
category={category}
isCard={false}
displayRuntimeInfo={displayRuntimeInfo}
/>
<ConnectorSummaryCard connector={operationApi} category={category} isCard={false} displayRuntimeInfo={displayRuntimeInfo} />
<Link className="msla-op-search-group-see-more" onClick={() => onConnectorClick(id)}>
{seeMoreText}
</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import NoResultsSvg from '../../../assets/search/noResults.svg';
import { AriaSearchResultsAlert } from '../../ariaSearchResults/ariaSearchResultsAlert';
import { isBuiltInConnector } from '../../connectors';
import { getConnectorCategoryString } from '../../utils';
import type { OperationActionData } from './interfaces';
import { OperationSearchCard } from './operationSearchCard';
import { OperationSearchGroup } from './operationSearchGroup';
import { List, Text } from '@fluentui/react';
import { Spinner } from '@fluentui/react-components';
import type { DiscoveryOpArray, DiscoveryOperation, DiscoveryResultTypes } from '@microsoft/logic-apps-shared';
import { isBuiltInConnector } from '@microsoft/logic-apps-shared';
import type { PropsWithChildren } from 'react';
import React, { useMemo } from 'react';
import { useIntl } from 'react-intl';
Expand Down Expand Up @@ -140,9 +140,9 @@ export const OperationActionDataFromOperation = (operation: DiscoveryOperation<D
brandColor: operation.properties.api.brandColor,
iconUri: operation.properties.api.iconUri,
connectorName: operation.properties.api.displayName,
category: getConnectorCategoryString(operation.properties.api.id),
category: getConnectorCategoryString(operation.properties.api),
isTrigger: !!operation.properties?.trigger,
isBuiltIn: isBuiltInConnector(operation.properties.api.id),
isBuiltIn: isBuiltInConnector(operation.properties.api),
apiId: operation.properties.api.id,
releaseStatus: operation.properties.annotation?.status,
});
8 changes: 5 additions & 3 deletions libs/designer-ui/src/lib/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { isBuiltInConnector, isCustomConnector } from '../connectors';
import Constants from '../constants';
import { getIntl, equals, isBuiltInConnector, isCustomConnector } from '@microsoft/logic-apps-shared';
import type { Connector, OperationApi } from '@microsoft/logic-apps-shared';
import { equals, getIntl } from '@microsoft/logic-apps-shared';

/**
* Returns a string with a duration, possibly abbreviated, e.g., 15s or 15 second(s)
Expand Down Expand Up @@ -347,7 +349,7 @@ export const filterRecord = <T>(data: Record<string, T>, filter: (_key: string,
.reduce((res: any, [key, value]: any) => ({ ...res, [key]: value }), {});
};

export const getConnectorCategoryString = (connectorId: string): string => {
export const getConnectorCategoryString = (connector: Connector | OperationApi | string): string => {
ek68794998 marked this conversation as resolved.
Show resolved Hide resolved
const intl = getIntl();
const builtInText = intl.formatMessage({
defaultMessage: 'In App',
Expand All @@ -362,7 +364,7 @@ export const getConnectorCategoryString = (connectorId: string): string => {
description: 'Custom category name text',
});

return isBuiltInConnector(connectorId) ? builtInText : isCustomConnector(connectorId) ? customText : azureText;
return isBuiltInConnector(connector) ? builtInText : isCustomConnector(connector) ? customText : azureText;
};

export const convertUIElementNameToAutomationId = (uiElementName: string): string => {
Expand Down
10 changes: 5 additions & 5 deletions libs/designer/src/lib/core/actions/bjsworkflow/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import { initializeTokensAndVariables } from '../../state/tokens/tokensSlice';
import type { WorkflowState } from '../../state/workflow/workflowInterfaces';
import { addNode, setFocusNode } from '../../state/workflow/workflowSlice';
import type { AppDispatch, RootState } from '../../store';
import { getBrandColorFromConnector, getBrandColorFromManifest, getIconUriFromConnector, getIconUriFromManifest } from '../../utils/card';
import { getBrandColorFromManifest, getIconUriFromManifest } from '../../utils/card';
import { getTriggerNodeId, isRootNodeInGraph } from '../../utils/graph';
import { updateDynamicDataInNode } from '../../utils/parameters/helper';
import { getInputParametersFromSwagger, getOutputParametersFromSwagger } from '../../utils/swagger/operation';
import { getTokenNodeIds, getBuiltInTokens, convertOutputsToTokens } from '../../utils/tokens';
import { setVariableMetadata, getVariableDeclarations, getAllVariables } from '../../utils/variables';
import { convertOutputsToTokens, getBuiltInTokens, getTokenNodeIds } from '../../utils/tokens';
import { getAllVariables, getVariableDeclarations, setVariableMetadata } from '../../utils/variables';
import { isConnectionRequiredForOperation, updateNodeConnection } from './connections';
import {
getInputParametersFromManifest,
Expand All @@ -31,14 +31,14 @@ import type { Settings } from './settings';
import { getOperationSettings, getSplitOnValue } from './settings';
import { ConnectionService, OperationManifestService, StaticResultService } from '@microsoft/designer-client-services-logic-apps';
import type {
SwaggerParser,
Connector,
DiscoveryOperation,
DiscoveryResultTypes,
OperationManifest,
SomeKindOfAzureOperationDiscovery,
SwaggerParser,
} from '@microsoft/logic-apps-shared';
import { ManifestParser, equals, getRecordEntry } from '@microsoft/logic-apps-shared';
import { ManifestParser, equals, getBrandColorFromConnector, getIconUriFromConnector, getRecordEntry } from '@microsoft/logic-apps-shared';
import type { Dispatch } from '@reduxjs/toolkit';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { batch } from 'react-redux';
Expand Down
25 changes: 13 additions & 12 deletions libs/designer/src/lib/core/actions/bjsworkflow/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { WorkflowKind } from '../../state/workflow/workflowInterfaces';
import type { WorkflowParameterDefinition } from '../../state/workflowparameters/workflowparametersSlice';
import { initializeParameters } from '../../state/workflowparameters/workflowparametersSlice';
import type { RootState } from '../../store';
import { getBrandColorFromConnector, getIconUriFromConnector } from '../../utils/card';
import { getTriggerNodeId, isRootNodeInGraph } from '../../utils/graph';
import { getSplitOnOptions, getUpdatedManifestForSchemaDependency, getUpdatedManifestForSplitOn, toOutputInfo } from '../../utils/outputs';
import {
Expand Down Expand Up @@ -48,33 +47,35 @@ import {
} from '@microsoft/designer-client-services-logic-apps';
import type { OutputToken, ParameterInfo } from '@microsoft/designer-ui';
import {
clone,
ConnectionReferenceKeyFormat,
CustomSwaggerServiceNames,
DynamicSchemaType,
equals,
getBrandColorFromConnector,
getIconUriFromConnector,
getIntl,
getObjectPropertyValue,
isDynamicListExtension,
isDynamicPropertiesExtension,
isDynamicSchemaExtension,
isDynamicTreeExtension,
isLegacyDynamicValuesExtension,
isLegacyDynamicValuesTreeExtension,
DynamicSchemaType,
ManifestParser,
PropertyName,
CustomSwaggerServiceNames,
UnsupportedException,
clone,
equals,
ConnectionReferenceKeyFormat,
unmap,
getObjectPropertyValue,
UnsupportedException,
} from '@microsoft/logic-apps-shared';
import type {
SchemaProperty,
InputParameter,
SwaggerParser,
OutputParameter,
CustomSwaggerServiceDetails,
InputParameter,
OperationInfo,
OperationManifest,
OperationManifestProperties,
OutputParameter,
SchemaProperty,
SwaggerParser,
} from '@microsoft/logic-apps-shared';
import type { Dispatch } from '@reduxjs/toolkit';

Expand Down
2 changes: 1 addition & 1 deletion libs/designer/src/lib/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ export { getOutputTokenSections, getExpressionTokenSections } from './utils/toke
export { getTriggerNodeId } from './utils/graph';
export { updateParameterValidation } from './state/operation/operationMetadataSlice';
export { updateWorkflowParameters } from './actions/bjsworkflow/initialize';
export { getBrandColorFromManifest, getIconUriFromManifest, getBrandColorFromConnector, getIconUriFromConnector } from './utils/card';
export { getBrandColorFromManifest, getIconUriFromManifest } from './utils/card';
20 changes: 2 additions & 18 deletions libs/designer/src/lib/core/utils/card.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Connector, OperationManifest } from '@microsoft/logic-apps-shared';
import { fallbackConnectorIconUrl } from '@microsoft/logic-apps-shared';
import type { OperationManifest } from '@microsoft/logic-apps-shared';
import { getBrandColorFromConnector, getIconUriFromConnector } from '@microsoft/logic-apps-shared';

export function getBrandColorFromManifest(manifest: OperationManifest): string {
return manifest.properties?.brandColor ?? getBrandColorFromConnector(manifest.properties?.connector);
Expand All @@ -8,19 +8,3 @@ export function getBrandColorFromManifest(manifest: OperationManifest): string {
export function getIconUriFromManifest(manifest: OperationManifest): string {
return manifest.properties?.iconUri ?? getIconUriFromConnector(manifest.properties?.connector);
}

export function getBrandColorFromConnector(connector: Connector | undefined): string {
if (!connector) return '#000000';
const {
properties: { brandColor, metadata },
} = connector;
return brandColor ?? metadata?.brandColor ?? '#000000';
}

export function getIconUriFromConnector(connector: Connector | undefined): string {
if (!connector) return '';
const {
properties: { iconUrl, iconUri, generalInformation },
} = connector;
return fallbackConnectorIconUrl(iconUrl ?? iconUri ?? generalInformation?.iconUrl);
}
Loading
Loading