Skip to content

Commit

Permalink
feat: publishing page (#2440)
Browse files Browse the repository at this point in the history
* re-enable publish page in settings

* add description to publish plugin, fix behavior of "Default" local test target

* add publish page in nav

* add publish page

* remove unrelevant

* add publish page and add getHistory method in localpublish plugin

* fix bug

* polish style

* add mock remote publish for development/testing purposes

* add alerts on failed validation

* add validation chck

* update typedefs
flatten and standardize result payload

* poll for updated stats if status is 202

* fix build fail

* add sensitive setting back and polish UI

* fix localpublish

* add testInEmulator button

* add group in detail list

* add log output

* bind the log button to the actual log content from the publish process

* add supported features to return value of types api

* add default into profile

* remove default show in profile list

* add rollback methods

* add rollback endpoint, mock rollback feature, rollback button in ui

* fix build

* change status show in list to icon

* polish

* fix e2e test

* disable open in emulator button, clean consoles

* change date format

* add targetName into url

* add delete in target list, add edit placehold in target list

* fix save target

* fix selectVersion after changing target

* implement confirm on delete of target
implement edit of target

* add error correction
implement edit/delete

* fix name undefined when adding target:

* fix some comments

* fix comment

* redirect to new target after create and edit, and fix date sort

* fix comments

* fix history update after publish fail

* resolve comments from cwhitten

* fix some code issues

Co-authored-by: Wenyi Luo <wenyluo@microsoft.com>
Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com>
  • Loading branch information
3 people authored Apr 17, 2020
1 parent d493b11 commit 5e4b35a
Show file tree
Hide file tree
Showing 40 changed files with 2,644 additions and 404 deletions.
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,6 @@ DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
Expand Down
3 changes: 2 additions & 1 deletion Composer/cypress/integration/LuisDeploy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
context('Luis Deploy', () => {
beforeEach(() => {
cy.server();
cy.route('POST', '/api/publish/*/publish/default', 'OK');
cy.route('POST', '/api/publish/*/publish/default', { endpointURL: 'anything' });
cy.route('POST', '/api/projects/*/settings', 'OK');
cy.route('GET', '/api/publish/*/status/default', { endpointURL: 'anything' });
cy.visit(Cypress.env('COMPOSER_URL'));
cy.createBot('ToDoBotWithLuisSample');
});
Expand Down
5 changes: 1 addition & 4 deletions Composer/cypress/integration/SaveAs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@
context('Saving As', () => {
beforeEach(() => {
cy.visit(Cypress.env('COMPOSER_URL'));
cy.createBot('ToDoBotWithLuisSample');
cy.createBot('EchoBot', 'TestBot');
});

it('can create a new bot from an existing bot', () => {
cy.findByTestId('LeftNav-CommandBarButtonHome').click();
cy.url().should('contain', 'home');
cy.findByText('Save as').click();

cy.findByTestId('NewDialogName').type('{selectall}__TestSaveAs{enter}');

cy.findByTestId('ProjectTree').within(() => {
cy.findByText('__TestSaveAs.Main').should('exist');
cy.findByText('View').should('exist');
});
});
});
7 changes: 7 additions & 0 deletions Composer/packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ const topLinks = (projectId: string, openedDialogId: string) => {
exact: true,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/publish`,
iconName: 'CloudUpload',
labelName: formatMessage('Publish'),
exact: true,
disabled: !botLoaded,
},
{
to: `/bot/${projectId}/skills`,
iconName: 'PlugDisconnected',
Expand Down
23 changes: 5 additions & 18 deletions Composer/packages/client/src/components/TestController/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,22 @@ import React, { useState, useRef, Fragment, useContext, useEffect, useCallback }
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import formatMessage from 'format-message';

import { DefaultPublishConfig } from '../../constants';

import settingsStorage from './../../utils/dialogSettingStorage';
import { StoreContext } from './../../store';
import { BotStatus, LuisConfig } from './../../constants';
import { isAbsHosted } from './../../utils/envUtil';
import { getReferredFiles } from './../../utils/luUtil';
import useNotifications from './../../pages/notifications/useNotifications';
import { navigateTo } from './../../utils';
import { navigateTo, openInEmulator } from './../../utils';
import { PublishLuisDialog } from './publishDialog';
import { bot, botButton } from './styles';
import { ErrorCallout } from './errorCallout';
import { EmulatorOpenButton } from './emulatorOpenButton';
import { Loading } from './loading';
import { ErrorInfo } from './errorInfo';

const openInEmulator = (url, authSettings: { MicrosoftAppId: string; MicrosoftAppPassword: string }) => {
// this creates a temporary hidden iframe to fire off the bfemulator protocol
// and start up the emulator
const i = document.createElement('iframe');
i.style.display = 'none';
i.onload = () => i.parentNode && i.parentNode.removeChild(i);
i.src = `bfemulator://livechat.open?botUrl=${encodeURIComponent(url)}&msaAppId=${
authSettings.MicrosoftAppId
}&msaAppPassword=${encodeURIComponent(authSettings.MicrosoftAppPassword)}`;
document.body.appendChild(i);
};

const defaultPublishConfig = {
name: 'default',
};
export const TestController: React.FC = () => {
const { state, actions } = useContext(StoreContext);
const [modalOpen, setModalOpen] = useState(false);
Expand All @@ -53,7 +40,7 @@ export const TestController: React.FC = () => {

useEffect(() => {
if (projectId) {
getPublishStatus(projectId, defaultPublishConfig);
getPublishStatus(projectId, DefaultPublishConfig);
}
}, [projectId]);

Expand Down Expand Up @@ -95,7 +82,7 @@ export const TestController: React.FC = () => {
async function handleLoadBot() {
setBotStatus(BotStatus.reloading);
const sensitiveSettings = settingsStorage.get(botName);
await publishToTarget(state.projectId, { ...defaultPublishConfig, sensitiveSettings });
await publishToTarget(state.projectId, DefaultPublishConfig, { comment: '' }, sensitiveSettings);
}

function isLuisConfigComplete(config) {
Expand Down
8 changes: 7 additions & 1 deletion Composer/packages/client/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ export enum ActionTypes {
PUBLISH_SUCCESS = 'PUBLISH_SUCCESS',
PUBLISH_FAILED = 'PUBLISH_FAILED',
GET_PUBLISH_STATUS = 'GET_PUBLISH_STATUS',
GET_PUBLISH_STATUS_FAILED = 'GET_PUBLISH_STATUS_FAILED',
GET_PUBLISH_HISTORY = 'GET_PUBLISH_HISTORY',
UPDATE_BOTSTATUS = 'UPDATE_BOTSTATUS',

SET_USER_SETTINGS = 'SET_USER_SETTINGS',
ADD_SKILL_DIALOG_BEGIN = 'ADD_SKILL_DIALOG_BEGIN',
ADD_SKILL_DIALOG_END = 'ADD_SKILL_DIALOG_END',
Expand Down Expand Up @@ -221,3 +222,8 @@ export const SupportedFileTypes = [
];

export const USER_TOKEN_STORAGE_KEY = 'composer.userToken';

export const DefaultPublishConfig = {
name: 'default',
type: 'localpublish',
};
1 change: 0 additions & 1 deletion Composer/packages/client/src/pages/design/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export const projectContainer = css`
flex-grow: 0;
flex-shrink: 0;
width: 255px;
height: 100%;
overflow: auto;
border-right: 1px solid #c4c4c4;
`;
Expand Down
88 changes: 88 additions & 0 deletions Composer/packages/client/src/pages/publish/createPublishTarget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/** @jsx jsx */
import { jsx } from '@emotion/core';
import formatMessage from 'format-message';
import { Dropdown } 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 { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { JsonEditor } from '@bfc/code-editor';

import { label } from './styles';

export const CreatePublishTarget = props => {
const [targetType, setTargetType] = useState(props.current ? props.current.type : '');
const [name, setName] = useState(props.current ? props.current.name : '');
const [config, setConfig] = useState(props.current ? JSON.parse(props.current.configuration) : {});
const [errorMessage, setErrorMsg] = useState('');

const updateType = (e, type) => {
setTargetType(type.key);
};
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;
if (exists) {
setErrorMsg(formatMessage('A profile with that name already exists.'));
}
}
};

const isDisable = () => {
if (!targetType || !name || errorMessage) {
return true;
} else {
return false;
}
};

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

return (
<Fragment>
<form onSubmit={submit}>
<TextField
placeholder="My Publish Profile"
defaultValue={props.current ? props.current.name : null}
label={formatMessage('Name')}
onChange={updateName}
errorMessage={errorMessage}
/>
<Dropdown
placeholder={formatMessage('Choose One')}
label={formatMessage('Publish Destination Type')}
options={props.targetTypes}
defaultSelectedKey={props.current ? props.current.type : null}
onChange={updateType}
/>
<div css={label}>{formatMessage('Paste Configuration')}</div>
<JsonEditor onChange={updateConfig} height={200} value={config} />
</form>
<DialogFooter>
<DefaultButton onClick={props.closeDialog} text={formatMessage('Cancel')} />
<PrimaryButton onClick={submit} disabled={isDisable()} text={formatMessage('Save')} />
</DialogFooter>
</Fragment>
);
};
Loading

0 comments on commit 5e4b35a

Please sign in to comment.