Skip to content

Commit

Permalink
Merge pull request #217 from botpress/robgordon-dev-2138-add-warning-…
Browse files Browse the repository at this point in the history
…message-if-content-used-in

feat(studio): add warning when editing shared content
  • Loading branch information
rob-gordon committed Dec 8, 2021
2 parents eecee77 + 96e4611 commit bb8bc30
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 83 deletions.
45 changes: 25 additions & 20 deletions packages/studio-ui/src/web/components/Content/Select/Widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import withLanguage from '../../Util/withLanguage'
import CreateOrEditModal from '../CreateOrEditModal'

import style from './style.scss'
import { WidgetContext } from './WidgetContext'

interface DispatchProps {
deleteMedia: (formData: any) => Promise<void>
Expand Down Expand Up @@ -125,27 +126,31 @@ class ContentPickerWidget extends Component<Props, State> {
}

return (
<ControlGroup fill>
<div
className={style.clickableInput}
onClick={() => (contentItem ? this.editItem() : window.botpress.pickContent({ contentType }, this.onChange))}
>
<InputGroup
placeholder={placeholder}
value={textContent}
disabled
id={inputId || ''}
className={style.contentInput}
<WidgetContext.Provider value={{ itemId: this.props.itemId }}>
<ControlGroup fill>
<div
className={style.clickableInput}
onClick={() =>
contentItem ? this.editItem() : window.botpress.pickContent({ contentType }, this.onChange)
}
>
<InputGroup
placeholder={placeholder}
value={textContent}
disabled
id={inputId || ''}
className={style.contentInput}
/>
{contentItem && <Button icon="edit" className={Classes.FIXED} />}
</div>
<Button
icon="folder-open"
onClick={() => window.botpress.pickContent({ contentType }, this.onChange)}
className={Classes.FIXED}
/>
{contentItem && <Button icon="edit" className={Classes.FIXED} />}
</div>
<Button
icon="folder-open"
onClick={() => window.botpress.pickContent({ contentType }, this.onChange)}
className={Classes.FIXED}
/>
{this.renderModal()}
</ControlGroup>
{this.renderModal()}
</ControlGroup>
</WidgetContext.Provider>
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createContext } from 'react'

export const WidgetContext = createContext({
itemId: ''
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Callout, Intent } from '@blueprintjs/core'
import { lang } from 'botpress/shared'
import React, { useContext } from 'react'
import { connect } from 'react-redux'
import { RootReducer } from '~/reducers'
import { FlowReducer } from '~/reducers/flows'
import { WidgetContext } from '../Content/Select/WidgetContext'
import { getContentItemUsage } from '../Shared/Utils'
import withLanguage from '../Util/withLanguage'

import style from './style.scss'

/** Displays a warning to user if cms content used in multiple places */
export function ContentNotice({ flows, qnaUsage }: { flows: FlowReducer; qnaUsage: any }) {
const { itemId } = useContext(WidgetContext)
const usage = itemId ? getContentItemUsage(itemId, flows, qnaUsage) : []
if (usage.length <= 1) {
return null
}
return (
<Callout
className={style.contentNotice}
title={lang.tr('contentNotice.title')}
intent={Intent.PRIMARY}
icon="info-sign"
>
<p>{lang.tr('contentNotice.message', { count: usage.length })}</p>
</Callout>
)
}

const mapStateToProps = (state: RootReducer) => ({
flows: state.flows,
qnaUsage: state.content.qnaUsage
})
export default connect(mapStateToProps)(withLanguage(ContentNotice))
30 changes: 17 additions & 13 deletions packages/studio-ui/src/web/components/ContentForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import style from '~/views/FlowBuilder/sidePanelTopics/form/style.scss'
import withLanguage from '../Util/withLanguage'

import ArrayFieldTemplate from './ArrayFieldTemplate'
import ContentNotice from './ContentNotice'
import FlowPickWidget from './FlowPickWidget'
import ArrayMl from './i18n/Array'
import renderWrapped from './i18n/I18nWrapper'
Expand Down Expand Up @@ -182,19 +183,22 @@ const ContentForm: FC<Props> = props => {
}

return (
<Form<FormData>
{...props}
formData={currentFormData}
formContext={context}
safeRenderCompletion
widgets={widgets}
fields={fields}
ArrayFieldTemplate={ArrayFieldTemplate}
onChange={handleOnChange}
schema={translatePropsRecursive(schema)}
>
{props.children}
</Form>
<>
<ContentNotice />
<Form<FormData>
{...props}
formData={currentFormData}
formContext={context}
safeRenderCompletion
widgets={widgets}
fields={fields}
ArrayFieldTemplate={ArrayFieldTemplate}
onChange={handleOnChange}
schema={translatePropsRecursive(schema)}
>
{props.children}
</Form>
</>
)
}

Expand Down
4 changes: 4 additions & 0 deletions packages/studio-ui/src/web/components/ContentForm/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@
.warning {
color: var(--lighthouse);
}

.contentNotice {
margin-bottom: 20px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Please do not change this file!
interface CssExports {
'actionsWrapper': string;
'contentNotice': string;
'flexContainer': string;
'missingIcon': string;
'warning': string;
Expand Down
58 changes: 57 additions & 1 deletion packages/studio-ui/src/web/components/Shared/Utils/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { FlowNode } from 'botpress/sdk'
import { ActionBuilderProps, ContentElement, FlowNode } from 'botpress/sdk'
import { FlowView, NodeView } from 'common/typings'
import { FlowReducer } from '~/reducers/flows'
import { ContentElementUsage, ContentUsage } from '~/views/Content'

export { default as ElementPreview } from './ElementPreview'
export { toastSuccess, toastFailure, toastInfo, Timeout } from './Toaster'
Expand Down Expand Up @@ -38,3 +41,56 @@ export const getFlowLabel = (name: string) => {
return name
}
}

export const getContentItemUsage = (elementId: string, flows: FlowReducer, qnaUsage: ContentElementUsage[]) => {
const elementUsage: ContentUsage[] = []
Object.values(flows.flowsByName).forEach((flow: FlowView) => {
// Skip skill flows
if (flow.skillData) {
return
}

flow.nodes.forEach((node: NodeView) => {
const usage: ContentUsage = {
type: 'Flow',
name: flow.name,
node: node.name,
count: 0
}

const addUsage = (v: string | ActionBuilderProps) => {
if (typeof v === 'string' && v.startsWith(`say #!${elementId}`)) {
if (!usage.count) {
elementUsage.push(usage)
}
usage.count++
}
}

const addNodeUsage = (node: NodeView) => {
node.onEnter?.forEach(addUsage)
node.onReceive?.forEach(addUsage)
}

if (node.flow && node.type === 'skill-call') {
const nodeSubFlow = flows.flowsByName[node.flow]
nodeSubFlow?.nodes.forEach((node: NodeView) => {
addNodeUsage(node)
})
} else {
addNodeUsage(node)
}
})
})

const usage = qnaUsage?.[`#!${elementId}`]
usage &&
elementUsage.push({
type: 'Q&A',
id: usage.qna,
name: usage.qna.substr(usage.qna.indexOf('_') + 1),
count: usage.count
})

return elementUsage
}
52 changes: 3 additions & 49 deletions packages/studio-ui/src/web/views/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '~/actions'
import CreateOrEditModal from '~/components/Content/CreateOrEditModal'
import { Container } from '~/components/Shared/Interface'
import { getContentItemUsage } from '~/components/Shared/Utils'
import { isOperationAllowed } from '~/components/Shared/Utils/AccessControl'
import DocumentationProvider from '~/components/Util/DocumentationProvider'
import { RootReducer } from '~/reducers'
Expand Down Expand Up @@ -80,54 +81,7 @@ class ContentView extends Component<Props, State> {

currentContentType() {
this.props.contentItems.forEach((element: ContentElementUsage) => {
element.usage = []
Object.values(this.props.flows.flowsByName).forEach((flow: FlowView) => {
// Skip skill flows
if (flow.skillData) {
return
}

flow.nodes.forEach((node: NodeView) => {
const usage: ContentUsage = {
type: 'Flow',
name: flow.name,
node: node.name,
count: 0
}

const addUsage = (v: string | ActionBuilderProps) => {
if (typeof v === 'string' && v.startsWith(`say #!${element.id}`)) {
if (!usage.count) {
element.usage.push(usage)
}
usage.count++
}
}

const addNodeUsage = (node: NodeView) => {
node.onEnter?.forEach(addUsage)
node.onReceive?.forEach(addUsage)
}

if (node.flow && node.type === 'skill-call') {
const nodeSubFlow = this.props.flows.flowsByName[node.flow]
nodeSubFlow?.nodes.forEach((node: NodeView) => {
addNodeUsage(node)
})
} else {
addNodeUsage(node)
}
})
})

const usage = this.props.qnaUsage?.[`#!${element.id}`]
usage &&
element.usage.push({
type: 'Q&A',
id: usage.qna,
name: usage.qna.substr(usage.qna.indexOf('_') + 1),
count: usage.count
})
element.usage = getContentItemUsage(element.id, this.props.flows, this.props.qnaUsage)
})

return this.state.modifyId
Expand Down Expand Up @@ -317,7 +271,7 @@ interface State {
qnaUsage: any
}

type ContentElementUsage = {
export type ContentElementUsage = {
usage: ContentUsage[]
} & ContentElement

Expand Down
4 changes: 4 additions & 0 deletions packages/ui-shared/src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -281,5 +281,9 @@
"thisMonth": "This Month",
"thisWeek": "This Week",
"thisYear": "This Year"
},
"contentNotice": {
"title": "Changing Shared Content",
"message": "This content is used in {count} places. Changing it will affect all places."
}
}
4 changes: 4 additions & 0 deletions packages/ui-shared/src/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -281,5 +281,9 @@
"thisMonth": "Este Mes",
"thisWeek": "Esta Semana",
"thisYear": "Este Año"
},
"contentNotice": {
"title": "Cambio de contenido compartido",
"message": "Este contenido se utiliza en {count} lugares. Cambiarlo afectará a todos los lugares."
}
}
4 changes: 4 additions & 0 deletions packages/ui-shared/src/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -281,5 +281,9 @@
"thisMonth": "Ce mois-ci",
"thisWeek": "Cette semaine",
"thisYear": "Cette année"
},
"contentNotice": {
"title": "Modification du contenu partagé",
"message": "Ce contenu est utilisé à {count} endroits. Le changer affectera tous les endroits."
}
}

0 comments on commit bb8bc30

Please sign in to comment.