diff --git a/client/extensions/auto-tagger/src/auto-tagging.tsx b/client/extensions/auto-tagger/src/auto-tagging.tsx index 98354e57..3a17b003 100644 --- a/client/extensions/auto-tagger/src/auto-tagging.tsx +++ b/client/extensions/auto-tagger/src/auto-tagging.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import {OrderedMap, OrderedSet, Map} from 'immutable'; -import {Switch, Button, ButtonGroup, EmptyState, Autocomplete} from 'superdesk-ui-framework/react'; +import {Switch, Button, ButtonGroup, EmptyState, Autocomplete, Modal} from 'superdesk-ui-framework/react'; import {ToggleBoxNext} from 'superdesk-ui-framework'; -import {IArticle, ISuperdesk} from 'superdesk-api'; +import {IArticle, IArticleSideWidget, ISuperdesk} from 'superdesk-api'; import {getTagsListComponent} from './tag-list'; import {getNewItemComponent} from './new-item'; @@ -35,9 +35,7 @@ interface IAutoTaggingSearchResult { }; } -interface IProps { - article: IArticle; -} +type IProps = React.ComponentProps; interface ISemaphoreFields { [key: string]: { @@ -95,48 +93,11 @@ export function getAutoTaggingData(data: IEditableData, semaphoreConfig: any) { function showAutoTaggerServiceErrorModal(superdesk: ISuperdesk, errors: Array) { const {gettext} = superdesk.localization; const {showModal} = superdesk.ui; - const {Modal, ModalHeader, ModalBody, ModalFooter} = superdesk.components; showModal(({closeModal}) => ( - - - {gettext('Autotagger service error')} - - - -

{gettext('Some tags can not be displayed')}

- -

- { - gettext( - 'Autotagger service has returned tags ' - + 'referencing parents that do not exist in the response.', - ) - } -

- - - - - - - - - - { - errors.map((tag) => ( - - - - - - )) - } - -
{gettext('tag name')}{gettext('qcode')}{gettext('parent ID')}
{tag.name}{tag.qcode}{tag.parent}
-
- - + - + )} + > +

{gettext('Some tags can not be displayed')}

+ +

+ { + gettext( + 'Autotagger service has returned tags ' + + 'referencing parents that do not exist in the response.', + ) + } +

+ + + + + + + + + + { + errors.map((tag) => ( + + + + + + )) + } + +
{gettext('tag name')}{gettext('qcode')}{gettext('parent ID')}
{tag.name}{tag.qcode}{tag.parent}
)); } -export function getAutoTaggingComponent(superdesk: ISuperdesk, label: string) { +export function getAutoTaggingComponent(superdesk: ISuperdesk, label: string): IArticleSideWidget['component'] { const {preferences} = superdesk; const {httpRequestJsonLocal} = superdesk; const {gettext, gettextPlural} = superdesk.localization; const {memoize, generatePatch, arrayToTree} = superdesk.utilities; - const {WidgetHeading, Alert} = superdesk.components; + const {AuthoringWidgetLayout, AuthoringWidgetHeading, Alert} = superdesk.components; const groupLabels = getGroups(superdesk); const TagListComponent = getTagsListComponent(superdesk); @@ -411,388 +403,390 @@ export function getAutoTaggingComponent(superdesk: ISuperdesk, label: string) { const readOnly = superdesk.entities.article.isLockedInOtherSession(this.props.article); return ( - - { - (() => { - if (data === 'loading' || data === 'not-initialized') { - return null; - } else { - const treeErrors = arrayToTree( - data.changes.analysis.toArray(), - (item) => item.qcode, - (item) => item.parent, - ).errors; - - // only show errors when there are unsaved changes - if (treeErrors.length > 0 && dirty) { - return ( - { - showAutoTaggerServiceErrorModal(superdesk, treeErrors); - }, - icon: 'info-sign', - }, - ]} - /> - ); - } else { - return null; - } - } - })() - } - - - { - data === 'loading' || data === 'not-initialized' || !dirty ? null : ( -
- - - -
- ) - } -
- -
-
- {/* Run automatically button is hidden for the next release */} -
- - { - const newValue = !runAutomaticallyPreference; - - this.setState({runAutomaticallyPreference: newValue}); - - superdesk.preferences.set(RUN_AUTOMATICALLY_PREFERENCE, newValue); - - if (newValue && this.state.data === 'not-initialized') { - this.runAnalysis(); - } - }} - aria-label="Run automatically" - label={{content: gettext('Run automatically')}} - /> - -
+ + { + data === 'loading' || data === 'not-initialized' || !dirty ? null : ( +
+ + +
+ ) + } + + )} + body={( + { - data === 'loading' || data === 'not-initialized' ? null : ( - <> -
-
- { - let cancelled = false; - - httpRequestJsonLocal<{analysis: IAutoTaggingSearchResult}>({ - method: 'POST', - path: '/ai/', - payload: { - service: 'semaphore', - item: { - searchString, - }, + (() => { + if (data === 'loading' || data === 'not-initialized') { + return null; + } else { + const treeErrors = arrayToTree( + data.changes.analysis.toArray(), + (item) => item.qcode, + (item) => item.parent, + ).errors; + + // only show errors when there are unsaved changes + if (treeErrors.length > 0 && dirty) { + return ( + { + showAutoTaggerServiceErrorModal(superdesk, treeErrors); }, - }).then((res) => { - if (cancelled === true) { - return; - } - - const json_response = res.analysis.result.tags; - const result_data = res.analysis; - - const result = toClientFormat(json_response).toArray(); - - const withoutExistingTags = result.filter( - (searchTag) => !tagAlreadyExists(data, searchTag.qcode), - ); - - const withResponse = withoutExistingTags.map((tag) => ({ - // required for Autocomplete component - keyValue: tag.name, + icon: 'info-sign', + }, + ]} + /> + ); + } else { + return null; + } + } + })() + } - tag, +
+
+ {/* Run automatically button is hidden for the next release */} +
+ + { + const newValue = !runAutomaticallyPreference; - // required to get all parents when an item is selected - entireResponse: result_data, - })); + this.setState({runAutomaticallyPreference: newValue}); - // Assuming 'callback' is a function - // that takes the processed data - callback(withResponse); - }); + superdesk.preferences.set(RUN_AUTOMATICALLY_PREFERENCE, newValue); - return { - cancel: () => { - cancelled = true; - }, - }; - }} - listItemTemplate={(__item: any) => { - const _item: ITagUi = __item.tag; - - return ( -
- {_item.name} - - { - _item?.group?.value == null ? null : ( -

{_item.group.value}

- ) - } - - { - _item?.description == null ? null : ( -

{_item.description}

- ) - } -
- ); - }} - onSelect={(_value: any) => { - const tag: ITagUi = _value.tag; - const entireResponse: IAutoTaggingSearchResult = - _value.entireResponse; - - this.insertTagFromSearch(tag, data, entireResponse); - this.setState({ - tentativeTagName: '', - forceRenderKey: Math.random(), - }); - }} - onChange={noop} - /> -
-
-
-
- - ) - } -
- - {(() => { - if (data === 'loading') { - return ( -
-
+
- ); - } else if (data === 'not-initialized') { - return ( - - ); - } else { - const { - entitiesGroupedAndSorted, - othersGrouped, - } = getAutoTaggingData(data, this.semaphoreFields); - - const savedTags = data.original.analysis.keySeq().toSet(); - - let allGrouped = OrderedMap(); - - othersGrouped.forEach((tags, groupId) => { - if (tags != null && groupId != null) { - allGrouped = allGrouped.set(groupId, - - { - this.updateTags( - ids.reduce( - (analysis, id) => analysis.remove(id), - data.changes.analysis, - ), - data, - ); - }} - /> - , - ); - } - }); - // renders the tags in the entities group in the widget window - if (entitiesGroupedAndSorted.size > 0) { - allGrouped = allGrouped.set('entities', - - {entitiesGroupedAndSorted.map((tags, key) => ( -
-
- {groupLabels.get(key).plural} + + { + data === 'loading' || data === 'not-initialized' ? null : ( + <> +
+
+ { + let cancelled = false; + + httpRequestJsonLocal<{analysis: IAutoTaggingSearchResult}>({ + method: 'POST', + path: '/ai/', + payload: { + service: 'semaphore', + item: { + searchString, + }, + }, + }).then((res) => { + if (cancelled === true) { + return; + } + + const json_response = res.analysis.result.tags; + const result_data = res.analysis; + + const result = toClientFormat(json_response).toArray(); + + const withoutExistingTags = result.filter( + (searchTag) => !tagAlreadyExists(data, searchTag.qcode), + ); + + const withResponse = withoutExistingTags.map((tag) => ({ + // required for Autocomplete component + keyValue: tag.name, + + tag, + + // required to get all parents when an item is selected + entireResponse: result_data, + })); + + // Assuming 'callback' is a function + // that takes the processed data + callback(withResponse); + }); + + return { + cancel: () => { + cancelled = true; + }, + }; + }} + listItemTemplate={(__item: any) => { + const _item: ITagUi = __item.tag; + + return ( +
+ {_item.name} + + { + _item?.group?.value == null ? null : ( +

{_item.group.value}

+ ) + } + + { + _item?.description == null ? null : ( +

{_item.description}

+ ) + } +
+ ); + }} + onSelect={(_value: any) => { + const tag: ITagUi = _value.tag; + const entireResponse: IAutoTaggingSearchResult = + _value.entireResponse; + + this.insertTagFromSearch(tag, data, entireResponse); + this.setState({ + tentativeTagName: '', + forceRenderKey: Math.random(), + }); + }} + onChange={noop} + />
- { - this.updateTags( - ids.reduce( - (analysis, id) => analysis.remove(id), - data.changes.analysis, - ), - data, - ); +
+
+
- )).toArray()} - , - ); - } - - const allGroupedAndSortedByConfig = allGrouped - .filter((_, key) => hasConfig(key, this.semaphoreFields.others)) - .sortBy((_, key) => this.semaphoreFields.others[key].order, - (a, b) => a - b); - - const allGroupedAndSortedNotInConfig = allGrouped - .filter((_, key) => !hasConfig(key, this.semaphoreFields.others)); - - const allGroupedAndSorted = allGroupedAndSortedByConfig - .concat(allGroupedAndSortedNotInConfig); - - return ( - - { - this.state.newItem == null ? null : ( - { - this.setState({newItem}); - }} - save={(newItem: INewItem) => { - this.createNewTag(newItem, data); - }} - cancel={() => { - this.setState({newItem: null}); - }} - tagAlreadyExists={ - (qcode) => tagAlreadyExists(data, qcode) - } - /> - ) + + ) + } +
+ + {(() => { + if (data === 'loading') { + return ( +
+
+
+ ); + } else if (data === 'not-initialized') { + return ( + + ); + } else { + const { + entitiesGroupedAndSorted, + othersGrouped, + } = getAutoTaggingData(data, this.semaphoreFields); + + const savedTags = data.original.analysis.keySeq().toSet(); + + let allGrouped = OrderedMap(); + + othersGrouped.forEach((tags, groupId) => { + if (tags != null && groupId != null) { + allGrouped = allGrouped.set(groupId, + + { + this.updateTags( + ids.reduce( + (analysis, id) => analysis.remove(id), + data.changes.analysis, + ), + data, + ); + }} + /> + , + ); + } + }); + // renders the tags in the entities group in the widget window + if (entitiesGroupedAndSorted.size > 0) { + allGrouped = allGrouped.set('entities', + + {entitiesGroupedAndSorted.map((tags, key) => ( +
+
+ {groupLabels.get(key).plural} +
+ { + this.updateTags( + ids.reduce( + (analysis, id) => analysis.remove(id), + data.changes.analysis, + ), + data, + ); + }} + /> +
+ )).toArray()} +
, + ); } -
- {allGroupedAndSorted.map((item) => item).toArray()} -
- - ); - } - })()} - -
- {(() => { - if (data === 'loading') { - return null; - } else if (data === 'not-initialized') { - return ( -
-
- + const allGroupedAndSortedByConfig = allGrouped + .filter((_, key) => hasConfig(key, this.semaphoreFields.others)) + .sortBy((_, key) => this.semaphoreFields.others[key].order, + (a, b) => a - b); + + const allGroupedAndSortedNotInConfig = allGrouped + .filter((_, key) => !hasConfig(key, this.semaphoreFields.others)); + + const allGroupedAndSorted = allGroupedAndSortedByConfig + .concat(allGroupedAndSortedNotInConfig); + + return ( + + { + this.state.newItem == null ? null : ( + { + this.setState({newItem}); + }} + save={(newItem: INewItem) => { + this.createNewTag(newItem, data); + }} + cancel={() => { + this.setState({newItem: null}); + }} + tagAlreadyExists={ + (qcode) => tagAlreadyExists(data, qcode) + } + /> + ) + } + +
+ {allGroupedAndSorted.map((item) => item).toArray()} +
+
+ ); + } + })()} +
+ + )} + footer={(() => { + if (data === 'loading') { + return ; + } else if (data === 'not-initialized') { + return ( +