Skip to content

Commit

Permalink
feat(nlu): entity can be renamed and duplicated
Browse files Browse the repository at this point in the history
  • Loading branch information
asashour committed Jan 1, 2020
1 parent ebd3bde commit d315534
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 128 deletions.
8 changes: 7 additions & 1 deletion modules/nlu/src/backend/api.ts
Expand Up @@ -180,6 +180,12 @@ export default async (bp: typeof sdk, state: NLUState) => {
res.json(entities)
})

router.get('/entities/:entityName', async (req, res) => {
res.send(
await (state.nluByBot[req.params.botId].engine1 as ScopedEngine).storage.getCustomEntity(req.params.entityName)
)
})

router.post('/entities', async (req, res) => {
const { botId } = req.params
try {
Expand All @@ -203,7 +209,7 @@ export default async (bp: typeof sdk, state: NLUState) => {
const updatedEntity = content as sdk.NLU.EntityDefinition

const botEngine = state.nluByBot[botId].engine1 as ScopedEngine
await botEngine.storage.saveEntity({ ...updatedEntity, id })
await botEngine.storage.updateEntity(id, updatedEntity)
scheduleSyncNLU(req.params.botId)

res.sendStatus(200)
Expand Down
2 changes: 1 addition & 1 deletion modules/nlu/src/backend/entities.ts
Expand Up @@ -28,5 +28,5 @@ export const EntityDefCreateSchema = Joi.object().keys({
occurences: Joi.array()
.items(EntityDefOccurenceSchema)
.default([]),
pattern: Joi.string().default('')
pattern: Joi.string().default('').allow('')
})
16 changes: 16 additions & 0 deletions modules/nlu/src/backend/storage.ts
Expand Up @@ -232,11 +232,27 @@ export default class Storage {
})
}

async getCustomEntity(name: string): Promise<sdk.NLU.EntityDefinition> {
const entities = await this.getCustomEntities()
return entities.find(e => e.name === name)
}

async saveEntity(entity: sdk.NLU.EntityDefinition): Promise<void> {
const obj = _.omit(entity, ['id'])
return this.botGhost.upsertFile(this.entitiesDir, `${entity.id}.json`, JSON.stringify(obj, undefined, 2))
}

async updateEntity(targetEntityId: string, entity: sdk.NLU.EntityDefinition): Promise<void> {
const entities = await this.getCustomEntities()
const oldEntity = entities.find(e => e.id === targetEntityId)
const obj = _.omit(entity, ['id'])
if (oldEntity && oldEntity.name !== entity.name) {
this.botGhost.deleteFile(this.entitiesDir, `${targetEntityId}.json`)
}

return this.botGhost.upsertFile(this.entitiesDir, `${entity.id}.json`, JSON.stringify(obj, undefined, 2))
}

async deleteEntity(entityId: string): Promise<void> {
return this.botGhost.deleteFile(this.entitiesDir, `${entityId}.json`)
}
Expand Down
6 changes: 4 additions & 2 deletions modules/nlu/src/views/api.ts
Expand Up @@ -9,8 +9,9 @@ export interface NLUApi {
updateIntent: (targetIntent: string, intent: Partial<NLU.IntentDefinition>) => Promise<any>
deleteIntent: (x: string) => Promise<any>
fetchEntities: () => Promise<NLU.EntityDefinition[]>
fetchEntity: (x: string) => Promise<NLU.EntityDefinition>
createEntity: (x: NLU.EntityDefinition) => Promise<any>
updateEntity: (x: NLU.EntityDefinition) => Promise<any>
updateEntity: (targetEntityId: string, x: NLU.EntityDefinition) => Promise<any>
deleteEntity: (x: string) => Promise<any>
}

Expand All @@ -26,7 +27,8 @@ export const makeApi = (bp: { axios: AxiosInstance }): NLUApi => ({
bp.axios.post(`/mod/nlu/intents/${targetIntent}`, intent),
deleteIntent: (name: string) => bp.axios.post(`/mod/nlu/intents/${name}/delete`),
fetchEntities: () => bp.axios.get('/mod/nlu/entities').then(res => res.data),
fetchEntity: (entityName: string) => bp.axios.get(`/mod/nlu/entities/${entityName}`).then(res => res.data),
createEntity: (entity: NLU.EntityDefinition) => bp.axios.post(`/mod/nlu/entities/`, entity),
updateEntity: (entity: NLU.EntityDefinition) => bp.axios.post(`/mod/nlu/entities/${entity.id}`, entity),
updateEntity: (targetEntityId: string, entity: NLU.EntityDefinition) => bp.axios.post(`/mod/nlu/entities/${targetEntityId}`, entity),
deleteEntity: (entityId: string) => bp.axios.post(`/mod/nlu/entities/${entityId}/delete`)
})
99 changes: 0 additions & 99 deletions modules/nlu/src/views/full/entities/CreateEntityModal.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion modules/nlu/src/views/full/entities/EntityEditor.tsx
Expand Up @@ -8,7 +8,7 @@ import { PatternEntityEditor } from './PatternEntity'

interface Props {
entity: NLU.EntityDefinition
updateEntity: (e: NLU.EntityDefinition) => void
updateEntity: (targetEntity: string, e: NLU.EntityDefinition) => void
}

export default (props: Props) => {
Expand Down
150 changes: 150 additions & 0 deletions modules/nlu/src/views/full/entities/EntityNameModal.tsx
@@ -0,0 +1,150 @@
import { Button, Callout, Classes, Dialog, FormGroup, HTMLSelect, Intent } from '@blueprintjs/core'
import { NLUApi } from 'api'
import { NLU } from 'botpress/sdk'
import _ from 'lodash'
import React, { FC, useEffect, useRef, useState } from 'react'

const AVAILABLE_TYPES = [
{
label: 'List',
value: 'list'
},
{
label: 'Pattern',
value: 'pattern'
}
]

interface Props {
api: NLUApi
// Used for actions rename and duplicate
originalName?: string
action: 'create' | 'rename' | 'duplicate'
entityIDs: string[]
isOpen: boolean
closeModal: () => void
onEntityModified: (ent: any) => void
}

export const EntityNameModal: FC<Props> = props => {
const [name, setName] = useState<string>('')
const [type, setType] = useState<string>(AVAILABLE_TYPES[0].value)
const [isValid, setIsValid] = useState<boolean>()

useEffect(() => {
setIsValid(name.trim().length > 0 && type !== undefined)
}, [name, type])

useEffect(() => {
props.action === 'rename' ? setName(props.originalName) : setName('')
}, [props.isOpen])

const submit = async e => {
e.preventDefault()

if (props.action === 'create') {
onCreateEntity()
} else if (props.action === 'duplicate') {
onDuplicateEntity()
} else if (props.action === 'rename') {
onRenameEntity()
}

props.closeModal()
}

const onCreateEntity = () => {
const entity = {
id: getEntityId(name),
name: name.trim(),
type: type as NLU.EntityType,
occurences: []
}
props.api.createEntity(entity).then(() => {
props.onEntityModified(entity)
})
}

const onRenameEntity = async () => {
const entity = await props.api.fetchEntity(props.originalName)
entity.name = name.trim()
entity.id = getEntityId(name)
props.api.updateEntity(getEntityId(props.originalName), entity).then(() => props.onEntityModified(entity))
}

const getEntityId = (entityName: string) => entityName.trim().toLowerCase().replace(/[\t\s]/g, '-')

const onDuplicateEntity = async () => {
const entity = await props.api.fetchEntity(props.originalName)
const clone = _.cloneDeep(entity)
clone.name = name.trim()
clone.id = getEntityId(name)
props.api.createEntity(clone).then(() => props.onEntityModified(clone))
}

const isIdentical = props.action === 'rename' && props.originalName === name
const alreadyExists = !isIdentical && _.some(props.entityIDs, id => id === getEntityId(name))

let dialog: { icon: any; title: string } = { icon: 'add', title: 'Create Entity' }
if (props.action === 'duplicate') {
dialog = { icon: 'duplicate', title: 'Duplicate Entity' }
} else if (props.action === 'rename') {
dialog = { icon: 'edit', title: 'Rename Entity' }
}

return (
<Dialog
isOpen={props.isOpen}
onClose={props.closeModal}
transitionDuration={0}
{...dialog}
>
<form onSubmit={submit}>
<div className={Classes.DIALOG_BODY}>
<FormGroup label="Name">
<input
required
name="name"
type="text"
tabIndex={1}
className={`${Classes.INPUT} ${Classes.FILL}`}
dir="auto"
placeholder="Entity name"
value={name}
onChange={e => setName(e.target.value)}
autoFocus
/>
</FormGroup>
{props.action === 'create' && (
<FormGroup label="Type">
<HTMLSelect
tabIndex={2}
fill
options={AVAILABLE_TYPES}
onChange={e => setType(e.target.value)}
value={type}
/>
</FormGroup>
)}

{alreadyExists && (
<Callout title="Name already in use" intent={Intent.DANGER}>
An entity with that name already exists. Please choose another one.
</Callout>
)}
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button
type="submit"
tabIndex={3}
intent={Intent.PRIMARY}
text="Submit"
disabled={!isValid || isIdentical || alreadyExists}
/>
</div>
</div>
</form>
</Dialog>
)
}
4 changes: 2 additions & 2 deletions modules/nlu/src/views/full/entities/ListEntity.tsx
Expand Up @@ -20,7 +20,7 @@ import { Occurence } from './ListEntityOccurence'

interface Props {
entity: NLU.EntityDefinition
updateEntity: (entity: NLU.EntityDefinition) => void
updateEntity: (targetEntity: string, entity: NLU.EntityDefinition) => void
}

const FuzzyTolerance = {
Expand All @@ -42,7 +42,7 @@ export const ListEntityEditor: React.FC<Props> = props => {
useEffect(() => {
const newEntity = { ...props.entity, fuzzy, occurences }
if (!_.isEqual(newEntity, props.entity)) {
props.updateEntity(newEntity)
props.updateEntity(newEntity.id, newEntity)
}
}, [occurences, fuzzy])

Expand Down
4 changes: 2 additions & 2 deletions modules/nlu/src/views/full/entities/PatternEntity.tsx
Expand Up @@ -19,7 +19,7 @@ import style from './style.scss'

interface Props {
entity: NLU.EntityDefinition
updateEntity: (entity: NLU.EntityDefinition) => void
updateEntity: (targetEntity: string, entity: NLU.EntityDefinition) => void
}

export const PatternEntityEditor: React.FC<Props> = props => {
Expand Down Expand Up @@ -60,7 +60,7 @@ export const PatternEntityEditor: React.FC<Props> = props => {
examples: examplesStr.trim().split('\n')
}
validateExamples()
props.updateEntity(newEntity)
props.updateEntity(newEntity.id, newEntity)
} catch (e) {
setPatternValid(false)
}
Expand Down

0 comments on commit d315534

Please sign in to comment.