diff --git a/src/routes/Apps/App/Actions.tsx b/src/routes/Apps/App/Actions.tsx index 5d11577b..33337849 100644 --- a/src/routes/Apps/App/Actions.tsx +++ b/src/routes/Apps/App/Actions.tsx @@ -122,7 +122,8 @@ class Actions extends React.Component { render() { // TODO: Look to move this up to the set state calls instead of forcing it to be on every render - const actions = this.getFilteredActions(); + const { actions } = this.props + const computedActions = this.getFilteredActions() return (
@@ -156,15 +157,38 @@ class Actions extends React.Component { componentRef={component => this.newActionButton = component} />
- this.onChangeSearchString(searchString)} - onSearch={searchString => this.onChangeSearchString(searchString)} - /> - + {actions.length === 0 + ?
+
+
Create an Action
+ this.onClickOpenActionEditor()} + ariaDescription={this.props.intl.formatMessage({ + id: FM.ACTIONS_CREATEBUTTONARIADESCRIPTION, + defaultMessage: 'Create a New Action' + })} + text={this.props.intl.formatMessage({ + id: FM.ACTIONS_CREATEBUTTONTITLE, + defaultMessage: 'New Action' + })} + /> +
+
+ : + this.onChangeSearchString(searchString)} + onSearch={searchString => this.onChangeSearchString(searchString)} + /> + + } { }) } render() { - let entityItems = this.getFilteredAndSortedEntities() + const { entities } = this.props + const computedEntities = this.getFilteredAndSortedEntities() return (
@@ -235,7 +236,7 @@ class Entities extends React.Component { id={FM.ENTITIES_SUBTITLE} defaultMessage="Entities hold values from the user or are set by code, and are stored in the bot's memory to track state" /> - + : Editing is only allowed in Master Tag } @@ -254,33 +255,56 @@ class Entities extends React.Component { })} componentRef={component => this.newEntityButton = component} /> -
- this.onChange(newValue)} - onSearch={(newValue) => this.onChange(newValue)} - /> - - column.render(entity, this)} - onRenderDetailsHeader={(detailsHeaderProps: OF.IDetailsHeaderProps, - defaultRender: OF.IRenderFunction) => - onRenderDetailsHeader(detailsHeaderProps, defaultRender)} - onColumnHeaderClick={this.onClickColumnHeader} - onActiveItemChanged={entity => this.onSelectEntity(entity)} + {entities.length === 0 + ?
+
+
Create an Entity
+ +
+
+ : + this.onChange(newValue)} + onSearch={(newValue) => this.onChange(newValue)} + /> + + column.render(entity, this)} + onRenderDetailsHeader={(detailsHeaderProps: OF.IDetailsHeaderProps, + defaultRender: OF.IRenderFunction) => + onRenderDetailsHeader(detailsHeaderProps, defaultRender)} + onColumnHeaderClick={this.onClickColumnHeader} + onActiveItemChanged={entity => this.onSelectEntity(entity)} + /> + } + ); diff --git a/src/routes/Apps/App/Index.css b/src/routes/Apps/App/Index.css index da426133..e55d9ae4 100644 --- a/src/routes/Apps/App/Index.css +++ b/src/routes/Apps/App/Index.css @@ -46,3 +46,15 @@ margin-top: 3px; } +.cl-page-placeholder { + min-height: 20em; + display: grid; + justify-content: center; + align-content: center; + text-align: center; +} + +.cl-page-placeholder__description { + margin-bottom: 1em; +} + diff --git a/src/routes/Apps/App/LogDialogs.tsx b/src/routes/Apps/App/LogDialogs.tsx index 7981e8f8..86b16c67 100644 --- a/src/routes/Apps/App/LogDialogs.tsx +++ b/src/routes/Apps/App/LogDialogs.tsx @@ -10,10 +10,11 @@ import * as OF from 'office-ui-fabric-react'; import { State } from '../../../types' import { AppBase, LogDialog, Session, ModelUtils, Teach, TeachWithHistory, TrainDialog, ActionBase, ReplayError, UIScoreInput } from '@conversationlearner/models' import { ChatSessionModal, LogDialogModal, TeachSessionModal } from '../../../components/modals' -import { - createChatSessionThunkAsync, +import { + createChatSessionThunkAsync, createTeachSessionFromHistoryThunkAsync, - createTeachSessionFromUndoThunkAsync } from '../../../actions/createActions' + createTeachSessionFromUndoThunkAsync +} from '../../../actions/createActions' import { deleteLogDialogThunkAsync } from '../../../actions/deleteActions'; import { fetchAllLogDialogsAsync, fetchHistoryThunkAsync } from '../../../actions/fetchActions'; import { setErrorDisplay } from '../../../actions/displayActions'; @@ -51,8 +52,7 @@ function getTagName(logDialog: LogDialog, component: LogDialogs): string { if (logDialog.packageId === component.props.app.devPackageId) { tagName = 'Master'; } - else - { + else { let packageVersion = component.props.app.packageVersions.find(pv => pv.packageId === logDialog.packageId); if (packageVersion) { tagName = packageVersion.packageVersion; @@ -144,7 +144,7 @@ function getColumns(intl: InjectedIntl): IRenderableColumn[] { render: logDialog => { let lastInput = getLastInput(logDialog) if (lastInput) { - return {lastInput}; + return {lastInput}; } return }, @@ -205,14 +205,14 @@ interface ComponentState { lastAction: ActionBase, teachSession: Teach, validationErrors: ReplayError[], - validationErrorTitleId: string | null, + validationErrorTitleId: string | null, validationErrorMessageId: string | null, } class LogDialogs extends React.Component { newChatSessionButton: OF.IButton state: ComponentState - + constructor(props: Props) { super(props) let columns = getColumns(this.props.intl); @@ -244,7 +244,7 @@ class LogDialogs extends React.Component { let firstValue = this.state.sortColumn.getSortValue(a, this) let secondValue = this.state.sortColumn.getSortValue(b, this) let compareValue = firstValue.localeCompare(secondValue) - + // If primary sort is the same do secondary sort on another column, to prevent sort jumping around if (compareValue === 0) { let sortColumn2 = ((this.state.sortColumn !== this.state.columns[1]) ? this.state.columns[1] : this.state.columns[2]) as IRenderableColumn @@ -327,26 +327,26 @@ class LogDialogs extends React.Component { } onClickLogDialogItem(logDialog: LogDialog) { - + // Convert to trainDialog until schema update change, and pass in app definition too let trainDialog = ModelUtils.ToTrainDialog(logDialog, this.props.actions, this.props.entities); ((this.props.fetchHistoryThunkAsync(this.props.app.appId, trainDialog, this.props.user.name, this.props.user.id) as any) as Promise) - .then(teachWithHistory => { - this.setState({ - history: teachWithHistory.history, - lastAction: teachWithHistory.lastAction, - currentLogDialog: logDialog, - isLogDialogWindowOpen: true, - validationErrors: teachWithHistory.replayErrors, - isValidationWarningOpen: teachWithHistory.replayErrors.length > 0, - validationErrorTitleId: FM.REPLAYERROR_LOGDIALOG_VALIDATION_TITLE, - validationErrorMessageId: FM.REPLAYERROR_LOGDIALOG_VALIDATION_MESSAGE + .then(teachWithHistory => { + this.setState({ + history: teachWithHistory.history, + lastAction: teachWithHistory.lastAction, + currentLogDialog: logDialog, + isLogDialogWindowOpen: true, + validationErrors: teachWithHistory.replayErrors, + isValidationWarningOpen: teachWithHistory.replayErrors.length > 0, + validationErrorTitleId: FM.REPLAYERROR_LOGDIALOG_VALIDATION_TITLE, + validationErrorMessageId: FM.REPLAYERROR_LOGDIALOG_VALIDATION_MESSAGE + }) + }) + .catch(error => { + console.warn(`Error when attempting to create history: `, error) }) - }) - .catch(error => { - console.warn(`Error when attempting to create history: `, error) - }) } @autobind @@ -363,7 +363,7 @@ class LogDialogs extends React.Component { } @autobind - onDeleteLogDialog() { + onDeleteLogDialog() { if (this.state.currentLogDialog) { this.props.deleteLogDialogThunkAsync(this.props.user.id, this.props.app, this.state.currentLogDialog.logDialogId, this.props.editingPackageId) } @@ -371,32 +371,32 @@ class LogDialogs extends React.Component { } onEditLogDialog(logDialogId: string, newTrainDialog: TrainDialog, newScoreInput: UIScoreInput) { - + // Create a new teach session from the train dialog ((this.props.createTeachSessionFromHistoryThunkAsync(this.props.app, newTrainDialog, this.props.user.name, this.props.user.id, newScoreInput) as any) as Promise) - .then(teachWithHistory => { - if (teachWithHistory.replayErrors.length === 0) { - // Note: Don't clear currentLogDialog so I can update it if I save my edits - this.setState({ - teachSession: teachWithHistory.teach, - history: teachWithHistory.history, - lastAction: teachWithHistory.lastAction, - isLogDialogWindowOpen: false, - isTeachDialogModalOpen: true - }) - } - else { - this.setState({ - validationErrors: teachWithHistory.replayErrors, - isValidationWarningOpen: true, - validationErrorTitleId: FM.REPLAYERROR_CONVERT_TITLE, - validationErrorMessageId: FM.REPLAYERROR_FAILMESSAGE - }) - } - }) - .catch(error => { - console.warn(`Error when attempting to create teach session from log dialog: `, error) - }) + .then(teachWithHistory => { + if (teachWithHistory.replayErrors.length === 0) { + // Note: Don't clear currentLogDialog so I can update it if I save my edits + this.setState({ + teachSession: teachWithHistory.teach, + history: teachWithHistory.history, + lastAction: teachWithHistory.lastAction, + isLogDialogWindowOpen: false, + isTeachDialogModalOpen: true + }) + } + else { + this.setState({ + validationErrors: teachWithHistory.replayErrors, + isValidationWarningOpen: true, + validationErrorTitleId: FM.REPLAYERROR_CONVERT_TITLE, + validationErrorMessageId: FM.REPLAYERROR_FAILMESSAGE + }) + } + }) + .catch(error => { + console.warn(`Error when attempting to create teach session from log dialog: `, error) + }) } @autobind @@ -426,28 +426,28 @@ class LogDialogs extends React.Component { }); ((this.props.createTeachSessionFromUndoThunkAsync(this.props.app.appId, this.state.teachSession, popRound, this.props.user.name, this.props.user.id) as any) as Promise) - .then(teachWithHistory => { - if (teachWithHistory.replayErrors.length === 0) { - this.setState({ - teachSession: teachWithHistory.teach, - history: teachWithHistory.history, - lastAction: teachWithHistory.lastAction - }) - } else { - this.setState({ - validationErrors: teachWithHistory.replayErrors, - isValidationWarningOpen: true, - validationErrorTitleId: FM.REPLAYERROR_UNDO_TITLE, - validationErrorMessageId: FM.REPLAYERROR_FAILMESSAGE - }) - } - }) - .catch(error => { - console.warn(`Error when attempting to create teach session from undo: `, error) - }) + .then(teachWithHistory => { + if (teachWithHistory.replayErrors.length === 0) { + this.setState({ + teachSession: teachWithHistory.teach, + history: teachWithHistory.history, + lastAction: teachWithHistory.lastAction + }) + } else { + this.setState({ + validationErrors: teachWithHistory.replayErrors, + isValidationWarningOpen: true, + validationErrorTitleId: FM.REPLAYERROR_UNDO_TITLE, + validationErrorMessageId: FM.REPLAYERROR_FAILMESSAGE + }) + } + }) + .catch(error => { + console.warn(`Error when attempting to create teach session from undo: `, error) + }) } - renderLogDialogItems(): LogDialog[] { + getFilteredAndSortedDialogs(): LogDialog[] { // Don't show log dialogs that have derived TrainDialogs as they've already been edited let filteredLogDialogs: LogDialog[] = this.props.logDialogs.filter(l => !l.targetTrainDialogIds || l.targetTrainDialogIds.length === 0); @@ -474,11 +474,12 @@ class LogDialogs extends React.Component { } render() { - let logDialogItems = this.renderLogDialogItems() + const { logDialogs } = this.props + const computedLogDialogs = this.getFilteredAndSortedDialogs() const currentLogDialog = this.state.currentLogDialog; return (
-
+
{ : Editing is only allowed in Master Tag } - {this.props.app.metadata.isLoggingOn === false && + {this.props.app.metadata.isLoggingOn === false && {
{ })} componentRef={component => this.newChatSessionButton = component} /> - -
- this.onChange(newValue)} - onSearch={(newValue) => this.onChange(newValue)} - /> -
- this.onClickSync()} - ariaDescription="Refresh" - text="Refresh" - iconProps={{ iconName: 'Sync' }} - />
- returnErrorStringWhenError(() => column.render(logDialog, this))} - onActiveItemChanged={logDialog => this.onClickLogDialogItem(logDialog)} + {logDialogs.length === 0 + ?
+
+
Create a Log Dialog
+ +
+
+ : + this.onChange(newValue)} + onSearch={(newValue) => this.onChange(newValue)} + /> +
+ this.onClickSync()} + ariaDescription="Refresh" + text="Refresh" + iconProps={{ iconName: 'Sync' }} + /> +
+ returnErrorStringWhenError(() => column.render(logDialog, this))} + onActiveItemChanged={logDialog => this.onClickLogDialogItem(logDialog)} + /> +
} + { logDialog={currentLogDialog} history={this.state.isLogDialogWindowOpen ? this.state.history : null} /> - { formattedMessageId={this.state.validationErrorMessageId} /> this.onUndoTeachStep(popRound)} - history={this.state.isTeachDialogModalOpen ? this.state.history : null} - lastAction={this.state.lastAction} - sourceLogDialog={this.state.currentLogDialog} + app={this.props.app} + editingPackageId={this.props.editingPackageId} + teach={this.props.teachSessions.current} + dialogMode={this.props.teachSessions.mode} + isOpen={this.state.isTeachDialogModalOpen} + onClose={this.onCloseTeachSession} + onUndo={(popRound) => this.onUndoTeachStep(popRound)} + history={this.state.isTeachDialogModalOpen ? this.state.history : null} + lastAction={this.state.lastAction} + sourceLogDialog={this.state.currentLogDialog} />
); diff --git a/src/routes/Apps/App/TrainDialogs.tsx b/src/routes/Apps/App/TrainDialogs.tsx index 7a581de3..27c43f66 100644 --- a/src/routes/Apps/App/TrainDialogs.tsx +++ b/src/routes/Apps/App/TrainDialogs.tsx @@ -52,14 +52,14 @@ function textClassName(trainDialog: TrainDialog): string { return OF.FontClassNames.mediumPlus; } -function getFirstInput(trainDialog: TrainDialog) : string { +function getFirstInput(trainDialog: TrainDialog): string { if (trainDialog.rounds && trainDialog.rounds.length > 0) { return trainDialog.rounds[0].extractorStep.textVariations[0].text } return null; } -function getLastInput(trainDialog: TrainDialog) : string { +function getLastInput(trainDialog: TrainDialog): string { if (trainDialog.rounds && trainDialog.rounds.length > 0) { return trainDialog.rounds[trainDialog.rounds.length - 1].extractorStep.textVariations[0].text; } @@ -76,7 +76,7 @@ function getLastResponse(trainDialog: TrainDialog, component: TrainDialogs): str let action = component.props.actions.find(a => a.actionId === actionId); if (action) { return ActionBase.GetPayload(action, getDefaultEntityMap(component.props.entities)) - } + } } } return null; @@ -99,9 +99,9 @@ function getColumns(intl: InjectedIntl): IRenderableColumn[] { let firstInput = getFirstInput(trainDialog); if (firstInput) { return ( - {trainDialog.invalid === true && } - {firstInput} - ) + {trainDialog.invalid === true && } + {firstInput} + ) } return }, @@ -220,8 +220,7 @@ class TrainDialogs extends React.Component { } sortTrainDialogs(trainDialogs: TrainDialog[]): TrainDialog[] { - - // If column header selected sort the items, always putting invald at the top + // If column header selected sort the items, always putting invalid at the top if (this.state.sortColumn) { trainDialogs .sort((a, b) => { @@ -273,14 +272,14 @@ class TrainDialogs extends React.Component { } toActionFilter(action: ActionBase, entities: EntityBase[]): OF.IDropdownOption { - return { + return { key: action.actionId, text: ActionBase.GetPayload(action, getDefaultEntityMap(entities)) } } - + toEntityFilter(entity: EntityBase): OF.IDropdownOption { - return { + return { key: entity.entityId, text: entity.entityName, data: entity.negativeId @@ -530,10 +529,10 @@ class TrainDialogs extends React.Component { }) } - renderTrainDialogItems(): TrainDialog[] { - let filteredTrainDialogs : TrainDialog[] = null; + getFilteredAndSortedDialogs(): TrainDialog[] { + let filteredTrainDialogs: TrainDialog[] = null; - if (!this.state.searchValue && ! this.state.entityFilter && !this.state.actionFilter) { + if (!this.state.searchValue && !this.state.entityFilter && !this.state.actionFilter) { filteredTrainDialogs = this.props.trainDialogs; } else { // TODO: Consider caching as not very efficient @@ -569,8 +568,8 @@ class TrainDialogs extends React.Component { // Filter out train dialogs that don't match filters (data = negativeId for multivalue) if (this.state.entityFilter && this.state.entityFilter.key - && !entitiesInTD.find(en => en.entityId === this.state.entityFilter.key) - && !entitiesInTD.find(en => en.entityId === this.state.entityFilter.data)) { + && !entitiesInTD.find(en => en.entityId === this.state.entityFilter.key) + && !entitiesInTD.find(en => en.entityId === this.state.entityFilter.data)) { return false; } if (this.state.actionFilter && this.state.actionFilter.key @@ -592,9 +591,9 @@ class TrainDialogs extends React.Component { } render() { - const { intl } = this.props - let trainDialogItems = this.renderTrainDialogItems() - let currentTrainDialog = this.state.currentTrainDialog + const { intl, trainDialogs } = this.props + const computedTrainDialogs = this.getFilteredAndSortedDialogs() + const currentTrainDialog = this.state.currentTrainDialog return (
@@ -629,75 +628,99 @@ class TrainDialogs extends React.Component { })} componentRef={component => this.newTeachSessionButton = component} /> - - this.onCloseTeachSession()} - onUndo={(popRound) => this.onUndoTeachStep(popRound)} - history={this.state.isTeachDialogModalOpen ? this.state.history : null} - lastAction={this.state.lastAction} - sourceTrainDialog={this.state.currentTrainDialog} - />
-
- - Search: - - this.onChangeSearchString(newValue)} - onSearch={(newValue) => this.onChangeSearchString(newValue)} - /> -
-
- e.positiveId == null) - .map(e => this.toEntityFilter(e)) - .concat({key: null, text: '---', data: null}) - } - /> - - this.toActionFilter(a, this.props.entities)) - .concat({key: null, text: '---'}) - } - /> -
- returnErrorStringWhenError(() => column.render(trainDialog, this))} - onActiveItemChanged={trainDialog => this.onClickTrainDialogItem(trainDialog)} + + {trainDialogs.length === 0 + ?
+
+
Create a Train Dialog
+ this.onClickNewTeachSession()} + ariaDescription={intl.formatMessage({ + id: FM.TRAINDIALOGS_CREATEBUTTONARIALDESCRIPTION, + defaultMessage: 'Create a New Train Dialog' + })} + text={intl.formatMessage({ + id: FM.TRAINDIALOGS_CREATEBUTTONTITLE, + defaultMessage: 'New Train Dialog' + })} + /> +
+
+ : +
+ + Search: + + this.onChangeSearchString(newValue)} + onSearch={(newValue) => this.onChangeSearchString(newValue)} + /> +
+
+ e.positiveId == null) + .map(e => this.toEntityFilter(e)) + .concat({ key: null, text: '---', data: null }) + } + /> + + this.toActionFilter(a, this.props.entities)) + .concat({ key: null, text: '---' }) + } + /> +
+ returnErrorStringWhenError(() => column.render(trainDialog, this))} + onActiveItemChanged={trainDialog => this.onClickTrainDialogItem(trainDialog)} + /> +
} + + this.onCloseTeachSession()} + onUndo={(popRound) => this.onUndoTeachStep(popRound)} + history={this.state.isTeachDialogModalOpen ? this.state.history : null} + lastAction={this.state.lastAction} + sourceTrainDialog={this.state.currentTrainDialog} />