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: add publish target configuration schema #2719

Merged
merged 12 commits into from
Apr 21, 2020
3 changes: 2 additions & 1 deletion Composer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"packages/extensions/*",
"packages/lib",
"packages/lib/*",
"packages/plugin-loader",
"packages/server",
"packages/test-utils",
"packages/tools",
Expand All @@ -28,7 +29,7 @@
"build:dev": "yarn build:lib && yarn build:tools && yarn build:extensions && yarn build:plugins",
"build:lib": "yarn workspace @bfc/libs build:all",
"build:electron": "yarn workspace @bfc/electron-server build",
"build:extensions": "wsrun -lt -p @bfc/extension @bfc/adaptive-form @bfc/visual-designer @bfc/ui-plugin-* -c build",
"build:extensions": "wsrun -lt -p @bfc/plugin-loader @bfc/extension @bfc/adaptive-form @bfc/visual-designer @bfc/ui-plugin-* -c build",
"build:server": "yarn workspace @bfc/server build",
"build:client": "yarn workspace @bfc/client build",
"build:tools": "yarn workspace @bfc/tools build:all",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,69 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import formatMessage from 'format-message';
import { Dropdown } from 'office-ui-fabric-react/lib/Dropdown';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { Fragment, useState } from 'react';
import { Fragment, useState, useMemo } from 'react';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { JsonEditor } from '@bfc/code-editor';

import { PublishTarget, PublishType } from '../../store/types';

import { label } from './styles';

export const CreatePublishTarget = props => {
const [targetType, setTargetType] = useState(props.current ? props.current.type : '');
interface CreatePublishTargetProps {
closeDialog: () => void;
current: PublishTarget | null;
targets: PublishTarget[];
types: PublishType[];
updateSettings: (name: string, type: string, configuration: string) => Promise<void>;
}

const CreatePublishTarget: React.FC<CreatePublishTargetProps> = props => {
const [targetType, setTargetType] = useState<string | undefined>(props.current?.type);
const [name, setName] = useState(props.current ? props.current.name : '');
const [config, setConfig] = useState(props.current ? JSON.parse(props.current.configuration) : {});
const [config, setConfig] = useState(props.current ? JSON.parse(props.current.configuration) : undefined);
const [errorMessage, setErrorMsg] = useState('');

const updateType = (e, type) => {
setTargetType(type.key);
const targetTypes = useMemo(() => {
return props.types.map(t => ({ key: t.name, text: t.name }));
}, [props.targets]);

const updateType = (_e, option?: IDropdownOption) => {
const type = props.types.find(t => t.name === option?.key);

if (type) {
setTargetType(type.name);
}
};

const updateConfig = newConfig => {
setConfig(newConfig);
};

const updateName = (e, newName) => {
setErrorMsg('');
setName(newName);
isNameValid(newName);
};

const isNameValid = newName => {
if (!newName || newName.trim() === '') {
setErrorMsg(formatMessage('Must have a name'));
} else {
const exists =
props.targets?.filter(t => {
return t.name.toLowerCase() === newName?.toLowerCase();
}).length > 0;
const exists = !!props.targets?.find(t => t.name.toLowerCase() === newName?.toLowerCase);

if (exists) {
setErrorMsg(formatMessage('A profile with that name already exists.'));
}
}
};

const schema = useMemo(() => {
return targetType ? props.types.find(t => t.name === targetType)?.schema : undefined;
}, [props.targets, targetType]);

const updateName = (e, newName) => {
setErrorMsg('');
setName(newName);
isNameValid(newName);
};

const isDisable = () => {
if (!targetType || !name || errorMessage) {
return true;
Expand All @@ -55,29 +76,32 @@ export const CreatePublishTarget = props => {
};

const submit = async () => {
await props.updateSettings(name, targetType, JSON.stringify(config, null, 2) || '{}');
props.closeDialog();
if (targetType) {
await props.updateSettings(name, targetType, JSON.stringify(config) || '{}');
props.closeDialog();
}
};

return (
<Fragment>
<form onSubmit={submit}>
<TextField
placeholder="My Publish Profile"
defaultValue={props.current ? props.current.name : null}
placeholder={formatMessage('My Publish Profile')}
defaultValue={props.current ? props.current.name : ''}
label={formatMessage('Name')}
onChange={updateName}
errorMessage={errorMessage}
/>
<Dropdown
placeholder={formatMessage('Choose One')}
label={formatMessage('Publish Destination Type')}
options={props.targetTypes}
options={targetTypes}
defaultSelectedKey={props.current ? props.current.type : null}
onChange={updateType}
/>
<div css={label}>{formatMessage('Paste Configuration')}</div>
<JsonEditor onChange={updateConfig} height={200} value={config} />
<div css={label}>{formatMessage('Publish Configuration')}</div>
<JsonEditor key={targetType} onChange={updateConfig} height={200} value={config} schema={schema} />
<button type="submit" hidden disabled={isDisable()} />
</form>
<DialogFooter>
<DefaultButton onClick={props.closeDialog} text={formatMessage('Cancel')} />
Expand All @@ -86,3 +110,5 @@ export const CreatePublishTarget = props => {
</Fragment>
);
};

export { CreatePublishTarget };
33 changes: 17 additions & 16 deletions Composer/packages/client/src/pages/publish/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import settingsStorage from '../../utils/dialogSettingStorage';
import { projectContainer } from '../design/styles';
import { StoreContext } from '../../store';
import { navigateTo } from '../../utils';
import { PublishTarget } from '../../store/types';

import { TargetList } from './targetList';
import { PublishDialog } from './publishDialog';
Expand Down Expand Up @@ -52,7 +53,7 @@ const Publish: React.FC<PublishPageProps> = props => {
type: DialogType.normal,
children: {},
});
const [editTarget, setEditTarget] = useState();
const [editTarget, setEditTarget] = useState<{ index: number; item: PublishTarget } | null>(null);

const isRollbackSupported = useMemo(
() => (target, version): boolean => {
Expand Down Expand Up @@ -210,8 +211,8 @@ const Publish: React.FC<PublishPageProps> = props => {
}
}, [thisPublishHistory, selectedTargetName]);

const savePublishTarget = useMemo(
() => async (name, type, configuration) => {
const savePublishTarget = useCallback(
async (name: string, type: string, configuration: string) => {
const _target = (settings.publishTargets || []).concat([
{
name,
Expand All @@ -233,8 +234,12 @@ const Publish: React.FC<PublishPageProps> = props => {
[settings.publishTargets, projectId, botName]
);

const updatePublishTarget = useMemo(
() => async (name, type, configuration) => {
const updatePublishTarget = useCallback(
async (name: string, type: string, configuration: string) => {
if (!editTarget) {
return;
}

const _targets = settings.publishTargets ? [...settings.publishTargets] : [];

_targets[editTarget.index] = {
Expand Down Expand Up @@ -264,10 +269,8 @@ const Publish: React.FC<PublishPageProps> = props => {
type: DialogType.normal,
children: (
<CreatePublishTarget
targetTypes={publishTypes.map(type => {
return { key: type.name, text: type.name };
})}
targets={settings.publishTargets}
types={publishTypes}
targets={settings.publishTargets || []}
updateSettings={savePublishTarget}
current={null}
closeDialog={() => setAddDialogHidden(true)}
Expand All @@ -282,11 +285,9 @@ const Publish: React.FC<PublishPageProps> = props => {
type: DialogType.normal,
children: (
<CreatePublishTarget
targetTypes={publishTypes.map(type => {
return { key: type.name, text: type.name };
})}
types={publishTypes}
current={editTarget ? editTarget.item : null}
targets={settings.publishTargets?.filter(item => editTarget && item.name != editTarget.item.name)}
targets={(settings.publishTargets || []).filter(item => editTarget && item.name != editTarget.item.name)}
updateSettings={updatePublishTarget}
closeDialog={() => setEditDialogHidden(true)}
/>
Expand Down Expand Up @@ -335,7 +336,7 @@ const Publish: React.FC<PublishPageProps> = props => {
[projectId, selectedTarget, settings.publishTargets]
);

const onEdit = async (index: number, item: any) => {
const onEdit = async (index: number, item: PublishTarget) => {
const newItem = { item: item, index: index };
setEditTarget(newItem);
setEditDialogHidden(false);
Expand Down Expand Up @@ -380,7 +381,7 @@ const Publish: React.FC<PublishPageProps> = props => {
onDismiss={() => setAddDialogHidden(true)}
dialogContentProps={dialogProps}
modalProps={{ isBlocking: true }}
minWidth={350}
minWidth={450}
>
{dialogProps.children}
</Dialog>
Expand All @@ -389,7 +390,7 @@ const Publish: React.FC<PublishPageProps> = props => {
onDismiss={() => setEditDialogHidden(true)}
dialogContentProps={editDialogProps}
modalProps={{ isBlocking: true }}
minWidth={350}
minWidth={450}
>
{editDialogProps.children}
</Dialog>
Expand Down
2 changes: 1 addition & 1 deletion Composer/packages/client/src/pages/skills/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const Skills: React.FC<RouteComponentProps> = () => {
settings={state.settings}
setSettings={actions.setSettings}
botId={state.settings.MicrosoftAppId}
skillHostEndpoint={state.settings.skillHostEndpoint}
skillHostEndpoint={state.settings.skillHostEndpoint as string | undefined}
/>
<SkillList skills={skills} projectId={projectId} />
</div>
Expand Down
8 changes: 5 additions & 3 deletions Composer/packages/client/src/store/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react';
import { PromptTab, BotSchemas, ProjectTemplate, DialogInfo, LgFile, LuFile, Skill, UserSettings } from '@bfc/shared';
import { JSONSchema7 } from '@bfc/extension';

import { CreationFlowStatus, BotStatus } from '../constants';

Expand Down Expand Up @@ -49,6 +50,7 @@ export interface StorageFolder extends File {
export interface PublishType {
name: string;
description: string;
schema?: JSONSchema7;
features: {
history: boolean;
publish: boolean;
Expand All @@ -59,8 +61,8 @@ export interface PublishType {

export interface PublishTarget {
name: string;
type: PublishType;
configuration: any;
type: string;
configuration: string;
}

export interface State {
Expand Down Expand Up @@ -142,7 +144,7 @@ export interface DialogSetting {
MicrosoftAppPassword?: string;
luis?: ILuisConfig;
publishTargets?: PublishTarget[];
[key: string]: any;
[key: string]: unknown;
}

export interface DesignPageLocation {
Expand Down
10 changes: 10 additions & 0 deletions Composer/packages/extensions/plugin-loader/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
extends: ['../../../.eslintrc.js'],
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
rules: {
'security/detect-non-literal-fs-filename': 'off',
},
};
1 change: 1 addition & 0 deletions Composer/packages/extensions/plugin-loader/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lib/
27 changes: 27 additions & 0 deletions Composer/packages/extensions/plugin-loader/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@bfc/plugin-loader",
"version": "1.0.0",
"description": "Exposes interfaces used by the Composer plugin system.",
"main": "lib/index.js",
"license": "MIT",
"private": true,
"scripts": {
"build": "yarn build:clean && yarn build:ts",
"build:ts": "tsc",
"build:clean": "rimraf build"
},
"devDependencies": {
"@types/express": "^4.17.6",
"@types/passport": "^1.0.3",
"@types/path-to-regexp": "^1.7.0",
"json-schema": "^0.2.5",
"rimraf": "^3.0.2",
"typescript": "^3.8.3"
},
"dependencies": {
"debug": "^4.1.1",
"globby": "^11.0.0",
"passport": "^0.4.1",
"path-to-regexp": "^6.1.0"
}
}
Loading