diff --git a/client/actions/events/api.ts b/client/actions/events/api.ts index 86a17af1a..0d5961acc 100644 --- a/client/actions/events/api.ts +++ b/client/actions/events/api.ts @@ -569,8 +569,9 @@ const save = (original, updates) => ( ) { delete eventUpdates.dates; } - eventUpdates.update_method = get(eventUpdates, 'update_method.value') || - EVENTS.UPDATE_METHODS[0].value; + eventUpdates.update_method = eventUpdates.update_method == null ? + EVENTS.UPDATE_METHODS[0].value : + eventUpdates.update_method?.value ?? eventUpdates.update_method; return originalEvent?._id != null ? planningApi.events.update(originalItem, eventUpdates) : diff --git a/client/actions/events/ui.ts b/client/actions/events/ui.ts index fc0003ce0..1bf43fea4 100644 --- a/client/actions/events/ui.ts +++ b/client/actions/events/ui.ts @@ -928,6 +928,7 @@ const save = (original, updates, confirmation, unlockOnClose) => ( { actionType: 'save', unlockOnClose: unlockOnClose, + large: true, } )); } diff --git a/client/actions/main.ts b/client/actions/main.ts index 5ee0ee992..95718be86 100644 --- a/client/actions/main.ts +++ b/client/actions/main.ts @@ -716,11 +716,7 @@ const openIgnoreCancelSaveModal = ({ const storedItems = itemType === ITEM_TYPE.EVENT ? selectors.events.storedEvents(getState()) : selectors.planning.storedPlannings(getState()); - - const item = { - ...get(storedItems, itemId) || {}, - ...autosaveData, - }; + const item = get(storedItems, itemId) || {}; if (!isExistingItem(item)) { delete item._id; @@ -749,7 +745,7 @@ const openIgnoreCancelSaveModal = ({ modalType: MODALS.IGNORE_CANCEL_SAVE, modalProps: { item: itemWithAssociatedData, - itemType: itemType, + updates: autosaveData, onCancel: onCancel, onIgnore: onIgnore, onSave: onSave, diff --git a/client/components/ConfirmationModal.tsx b/client/components/ConfirmationModal.tsx index 5c07b5763..b6485123a 100644 --- a/client/components/ConfirmationModal.tsx +++ b/client/components/ConfirmationModal.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import {gettext} from '../utils'; @@ -7,7 +6,30 @@ import {Modal} from './index'; import {ButtonList, Icon} from './UI'; import {KEYCODES} from '../constants'; -export class ConfirmationModal extends React.Component { +interface IProps { + handleHide(itemType?: string): void; + modalProps: { + onCancel?(): void; + cancelText?: string; + ignore?(): void; + showIgnore?: boolean; + ignoreText?: string; + okText?: string; + action?(): void; + title?: string; + body: React.ReactNode; + itemType?: string; + autoClose?: boolean; + large?: boolean; + bodyClassname?: string; + }; +} + +interface IState { + submitting: boolean; +} + +export class ConfirmationModal extends React.Component { constructor(props) { super(props); @@ -96,14 +118,18 @@ export class ConfirmationModal extends React.Component { } return ( - +

{modalProps.title || gettext('Confirmation')}

- +
{modalProps.body || gettext('Are you sure ?')}
@@ -115,23 +141,3 @@ export class ConfirmationModal extends React.Component { ); } } - -ConfirmationModal.propTypes = { - handleHide: PropTypes.func.isRequired, - modalProps: PropTypes.shape({ - onCancel: PropTypes.func, - cancelText: PropTypes.string, - ignore: PropTypes.func, - showIgnore: PropTypes.bool, - ignoreText: PropTypes.string, - okText: PropTypes.string, - action: PropTypes.func, - title: PropTypes.string, - body: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.element, - ]), - itemType: PropTypes.string, - autoClose: PropTypes.bool, - }), -}; diff --git a/client/components/Events/EventScheduleSummary/index.tsx b/client/components/Events/EventScheduleSummary/index.tsx index eb8e6ee59..a4fb349f6 100644 --- a/client/components/Events/EventScheduleSummary/index.tsx +++ b/client/components/Events/EventScheduleSummary/index.tsx @@ -1,24 +1,32 @@ import React from 'react'; -import PropTypes from 'prop-types'; import {get} from 'lodash'; +import {IEventItem} from 'interfaces'; +import {gettext, eventUtils, timeUtils} from '../../../utils'; + +import {FormLabel, Text, ContentDivider} from 'superdesk-ui-framework/react'; import {RepeatEventSummary} from '../RepeatEventSummary'; import {Row} from '../../UI/Preview'; -import {gettext, eventUtils, timeUtils} from '../../../utils'; + import './style.scss'; -import {IEventItem} from 'interfaces'; + + interface IProps { event: Partial, noPadding?: boolean, forUpdating?: boolean, useEventTimezone?: boolean + useFormLabelAndText?: boolean + addContentDivider?: boolean } export const EventScheduleSummary = ({ event, noPadding = false, forUpdating = false, - useEventTimezone = false + useEventTimezone = false, + useFormLabelAndText = false, + addContentDivider = false, }: IProps) => { if (!event) { return null; @@ -74,7 +82,32 @@ export const EventScheduleSummary = ({ currentDateLabel = gettext('Current Date (Based on Event timezone)'); } - return ( + return useFormLabelAndText ? ( + +
+ + + {currentDateText || ''} + +
+ {addContentDivider !== true ? null : ( + + )} + {doesRepeat !== true ? null : ( + +
+ + + {eventUtils.getRepeatSummaryForEvent(eventSchedule)} + +
+ {addContentDivider !== true ? null : ( + + )} +
+ )} +
+ ) : ( { + handleHide(itemType: IEventOrPlanningItem['_id']): void; + currentEditId: T['_id']; + modalProps: { + item: T; + updates: Partial; + onCancel(): void; + onIgnore(): void; + onSave( + withConfirmation: boolean, + updateMethod: string, + planningUpdateMethods: {[planningId: string]: IEventUpdateMethod} + ): void; + onGoTo(): void; + onSaveAndPost( + withConfirmation: boolean, + updateMethod: string, + planningUpdateMethods: {[planningId: string]: IEventUpdateMethod} + ): void; + title: string; + autoClose?: boolean; + bodyText?: string; + showIgnore?: boolean; + }; +} + +type IProps = IBaseProps | IBaseProps; -export class IgnoreCancelSaveModalComponent extends React.Component { - constructor(props) { +interface IState { + eventUpdateMethod: IEventUpdateMethod; + planningUpdateMethods: {[planningId: string]: IEventUpdateMethod}; +} + +export class IgnoreCancelSaveModalComponent extends React.Component { + constructor(props: IProps) { super(props); - this.state = {eventUpdateMethod: EVENTS.UPDATE_METHODS[0]}; + this.state = { + eventUpdateMethod: EVENTS.UPDATE_METHODS[0].value, + planningUpdateMethods: {}, + }; this.onEventUpdateMethodChange = this.onEventUpdateMethodChange.bind(this); + this.onPlanningUpdateMethodChange = this.onPlanningUpdateMethodChange.bind(this); this.onSubmit = this.onSubmit.bind(this); } - onEventUpdateMethodChange(field, option) { + onEventUpdateMethodChange(option: IEventUpdateMethod) { this.setState({eventUpdateMethod: option}); } - renderEvent() { - const {modalProps} = this.props; - - const { - item, - onSave, - } = modalProps || {}; - const {submitting} = this.state; - - const isRecurringEvent = eventUtils.isEventRecurring(item); - - return ( -
- - - - - - - {onSave && ( - - )} -
- ); + onPlanningUpdateMethodChange(planningId: IPlanningItem['_id'], updateMethod: IEventUpdateMethod) { + this.setState((prevState) => ({ + planningUpdateMethods: { + ...prevState.planningUpdateMethods, + [planningId]: updateMethod, + }, + })); } - renderPlanning() { - const {item} = get(this.props, 'modalProps') || {}; - - return ( -
- + +
+ ); + } else if (this.props.modalProps.item.type === 'event') { + return ( + { + this.props.handleHide(this.props.modalProps.item.type); + }, + unlockOnClose: false, + }} /> - - ); + ); + } + + return null; } onSubmit() { @@ -94,13 +110,15 @@ export class IgnoreCancelSaveModalComponent extends React.Component { } else if (onSaveAndPost) { return onSaveAndPost( false, - this.state.eventUpdateMethod + this.state.eventUpdateMethod, + this.state.planningUpdateMethods, ); } return onSave( false, - this.state.eventUpdateMethod + this.state.eventUpdateMethod, + this.state.planningUpdateMethods, ); } @@ -128,22 +146,9 @@ export class IgnoreCancelSaveModalComponent extends React.Component { return okText; } - getRenderItem(itemType) { - switch (itemType) { - case ITEM_TYPE.EVENT: - return this.renderEvent(); - - case ITEM_TYPE.PLANNING: - return this.renderPlanning(); - } - - return null; - } - render() { const {handleHide, modalProps} = this.props; const { - itemType, title, onIgnore, onCancel, @@ -163,37 +168,22 @@ export class IgnoreCancelSaveModalComponent extends React.Component { modalProps={{ onCancel: onCancel, cancelText: gettext('Cancel'), - showIgnore: isNil(showIgnore) ? true : showIgnore, + showIgnore: showIgnore !== true, ignore: onIgnore, ignoreText: gettext('Ignore'), action: (onGoTo || onSave || onSaveAndPost) ? this.onSubmit : null, okText: okText, title: title || gettext('Save Changes?'), - body: bodyText || this.getRenderItem(itemType), + body: bodyText || this.renderItemDetails(), autoClose: autoClose, + large: true, + bodyClassname: 'p-3', }} /> ); } } -IgnoreCancelSaveModalComponent.propTypes = { - handleHide: PropTypes.func.isRequired, - modalProps: PropTypes.shape({ - item: PropTypes.object, - itemType: PropTypes.string, - onCancel: PropTypes.func, - onIgnore: PropTypes.func, - onSave: PropTypes.func, - onGoTo: PropTypes.func, - onSaveAndPost: PropTypes.func, - title: PropTypes.string, - autoClose: PropTypes.bool, - bodyText: PropTypes.string, - }), - currentEditId: PropTypes.string, -}; - const mapStateToProps = (state) => ({ currentEditId: selectors.forms.currentItemId(state), }); diff --git a/client/components/ItemActionConfirmation/forms/updateRecurringEventsForm.tsx b/client/components/ItemActionConfirmation/forms/updateRecurringEventsForm.tsx index f26febe90..9284b4193 100644 --- a/client/components/ItemActionConfirmation/forms/updateRecurringEventsForm.tsx +++ b/client/components/ItemActionConfirmation/forms/updateRecurringEventsForm.tsx @@ -2,33 +2,42 @@ import React from 'react'; import {connect} from 'react-redux'; import {cloneDeep, isEqual} from 'lodash'; -import {IEventItem, IPlanningItem, IEventUpdateMethod, IEmbeddedCoverageItem} from '../../../interfaces'; +import { + IEmbeddedCoverageItem, + IEventFormProfile, + IEventItem, + IEventUpdateMethod, + IPlanningItem, + PREVIEW_PANEL, +} from '../../../interfaces'; import {planningApi} from '../../../superdeskApi'; import * as actions from '../../../actions'; -import {UpdateMethodSelection} from '../UpdateMethodSelection'; import {EVENTS, TEMP_ID_PREFIX} from '../../../constants'; -import {EventScheduleSummary} from '../../Events'; import {eventUtils, gettext} from '../../../utils'; -import {onItemActionModalHide, IModalProps} from './utils'; +import {IModalProps, onItemActionModalHide} from './utils'; import {storedPlannings} from '../../../selectors/planning'; +import {eventProfile} from '../../../selectors/forms'; -import {Row} from '../../UI/Preview'; -import {Select, Option} from 'superdesk-ui-framework/react'; +import {ContentDivider, Heading, Option, Select, Text, FormLabel} from 'superdesk-ui-framework/react'; import {PlanningMetaData} from '../../RelatedPlannings/PlanningMetaData'; +import {previewGroupToProfile, renderGroupedFieldsForPanel} from '../../fields'; +import {getUserInterfaceLanguageFromCV} from '../../../utils/users'; import '../style.scss'; interface IOwnProps { original: IEventItem; updates: Partial; - submitting: boolean; modalProps: IModalProps; - enableSaveInModal(): void; - resolve(item?: IEventItem): void; + enableSaveInModal?(): void; + resolve?(item?: IEventItem): void; + onEventUpdateMethodChange?(option: IEventUpdateMethod): void; + onPlanningUpdateMethodChange?(planningId: IPlanningItem['_id'], updateMethod: IEventUpdateMethod): void; } interface IStateProps { originalPlanningItems: {[planningId: string]: IPlanningItem}; + eventProfile: IEventFormProfile; } interface IDispatchProps { @@ -39,13 +48,8 @@ interface IDispatchProps { type IProps = IOwnProps & IStateProps & IDispatchProps; type IPlanningEmbeddedCoverageMap = {[planningId: string]: {[coverageId: string]: IEmbeddedCoverageItem}}; -interface IRecurringItemUpdateMethodOption { - name: string; - value: IEventUpdateMethod; -} - interface IState { - eventUpdateMethod: IRecurringItemUpdateMethodOption; + eventUpdateMethod: IEventUpdateMethod; relatedEvents: Array; relatedPlannings: Array; posting: boolean; @@ -60,7 +64,7 @@ function eventWasUpdated(original: IEventItem, updates: Partial): bo const originalItem = eventUtils.modifyForServer(cloneDeep(original)); const eventUpdates = eventUtils.getEventDiff(originalItem, updates); const eventFields = Object.keys(eventUpdates).filter( - (field) => !['update_method', 'dates'].includes(field) + (field) => !['update_method', 'dates', 'associated_plannings'].includes(field) ); return eventFields.length > 0; @@ -134,12 +138,12 @@ export class UpdateRecurringEventsComponent extends React.Component 0) { - return this.props.updates.associated_plannings - .filter((planningItem) => ( - this.state.recurringPlanningItemsToCreate.includes(planningItem._id) - )) - .map((planningItem) => ( -
- - -
- )); + renderModifiedPlanningItems() { + const planningsToCreate = (this.props.updates.associated_plannings || []) + .filter((planningItem) => ( + this.state.recurringPlanningItemsToCreate.includes(planningItem._id) + )); + const planningsToUpdate = (this.props.updates.associated_plannings || []) + .filter((planningItem) => ( + this.state.recurringPlanningItemsToUpdate.includes(planningItem._id) + )); + + if (planningsToCreate.length === 0 && planningsToUpdate.length === 0) { + return null; } - return null; + return ( + + + {gettext('Related Planning(s)')} + + {planningsToCreate.map((item, index) => ( + this.renderPlanningItem(item, false, index === planningsToCreate.length - 1) + ))} + {planningsToCreate.length === 0 || planningsToUpdate.length === 0 ? null : ( + + )} + {planningsToUpdate.map((item, index) => ( + this.renderPlanningItem(item, true, index === planningsToUpdate.length - 1) + ))} + + ); } - renderPlanningUpdateForm() { - if (this.state.recurringPlanningItemsToUpdate.length > 0) { - return this.props.updates.associated_plannings - .filter((planningItem) => ( - this.state.recurringPlanningItemsToUpdate.includes(planningItem._id) - )) - .map((planningItem) => ( -
- - -
- )); - } - - return null; + renderPlanningItem(item: Partial, planningExists: boolean, lastItem: boolean) { + return ( + + + {planningExists === true ? ( + + + {gettext('You made changes to this planning item that is part of a recurring event.')} + +  {gettext('Apply the changes to all recurring planning items or just this one?')} + + ) : ( + + + {gettext('You are creating a new planning item.')} + +  {gettext('Add this item to all recurring events or just this one?')} + + )} + + + + {lastItem === true ? null : ( + + )} + + ); } - onPlanningUpdateMethodChange(planningId: string, updateMethod: IEventUpdateMethod) { + onPlanningUpdateMethodChange(planningId: IPlanningItem['_id'], updateMethod: IEventUpdateMethod) { this.setState((prevState) => ({ planningUpdateMethods: { ...prevState.planningUpdateMethods, [planningId]: updateMethod, }, })); + if (this.props.onPlanningUpdateMethodChange != null) { + this.props.onPlanningUpdateMethodChange(planningId, updateMethod); + } } render() { - const {original, submitting} = this.props; + const {original} = this.props; const isRecurring = !!original.recurrence_id; const eventsInUse = this.state.relatedEvents.filter((e) => ( (e.planning_ids?.length ?? 0) > 0 || e.pubstatus != null @@ -276,50 +313,64 @@ export class UpdateRecurringEventsComponent extends React.Component +
    + {renderGroupedFieldsForPanel( + 'simple-preview', + previewGroupToProfile(PREVIEW_PANEL.EVENT, this.props.eventProfile, false), + { + item: { + ...this.props.original, + ...this.props.updates, + }, + language: this.props.updates.language ?? + this.props.original.language ?? + getUserInterfaceLanguageFromCV(), + useFormLabelAndText: true, + schema: this.props.eventProfile.schema, + profile: this.props.eventProfile, + addContentDivider: true, + }, + {}, + )} + {this.state.eventModified === false || isRecurring !== true ? null : ( + +
    + + + {numEvents} + +
    + +
    + )} +
+ {this.state.eventModified === false ? null : ( -
- - - - - - - - - + + {gettext('This is a recurring event.')} + {gettext('Update all recurring events or just this one?')} + + + )} - {this.renderPlanningCreateForm()} - {this.renderPlanningUpdateForm()} + {this.renderModifiedPlanningItems()} ); } @@ -327,6 +378,7 @@ export class UpdateRecurringEventsComponent extends React.Component ({ originalPlanningItems: storedPlannings(state), + eventProfile: eventProfile(state), }); const mapDispatchToProps = (dispatch: any, ownProps: IOwnProps) => ({ @@ -337,7 +389,7 @@ const mapDispatchToProps = (dispatch: any, ownProps: IOwnProps) => ({ planningApi.locks.unlockItem(savedItem); } - if (ownProps.resolve) { + if (ownProps.resolve != null) { ownProps.resolve(savedItem); } }) diff --git a/client/components/Main/ItemEditor/Editor.tsx b/client/components/Main/ItemEditor/Editor.tsx index 3d5997db0..a125c063e 100644 --- a/client/components/Main/ItemEditor/Editor.tsx +++ b/client/components/Main/ItemEditor/Editor.tsx @@ -225,22 +225,24 @@ export class EditorComponent extends React.Component }; const onSave = (isKilled || hasErrors) ? null : - (withConfirmation, updateMethod) => ( + (withConfirmation, updateMethod, planningUpdateMethods) => ( this.itemManager.save( withConfirmation, - updateMethod, + {name: updateMethod, value: updateMethod}, true, - updateStates + updateStates, + planningUpdateMethods ) ); const onSaveAndPost = (!isKilled || hasErrors) ? null : - (withConfirmation, updateMethod) => ( + (withConfirmation, updateMethod, planningUpdateMethods) => ( this.itemManager.saveAndPost( withConfirmation, updateMethod, true, - updateStates + updateStates, + planningUpdateMethods ) ); diff --git a/client/components/Main/ItemEditor/ItemManager.ts b/client/components/Main/ItemEditor/ItemManager.ts index 195dc6e83..ee91f7178 100644 --- a/client/components/Main/ItemEditor/ItemManager.ts +++ b/client/components/Main/ItemEditor/ItemManager.ts @@ -566,7 +566,8 @@ export class ItemManager { withConfirmation = true, updateMethod = EVENTS.UPDATE_METHODS[0], closeAfter = false, - updateStates = true + updateStates = true, + planningUpdateMethods = {} ) { return this._save({ post: false, @@ -575,6 +576,7 @@ export class ItemManager { updateMethod: updateMethod, closeAfter: closeAfter || this.shouldClose(), updateStates: updateStates, + planningUpdateMethods: planningUpdateMethods, }); } @@ -582,7 +584,8 @@ export class ItemManager { withConfirmation = true, updateMethod = EVENTS.UPDATE_METHODS[0], closeAfter = false, - updateStates = true + updateStates = true, + planningUpdateMethods = {} ) { return this._save({ post: true, @@ -591,6 +594,7 @@ export class ItemManager { updateMethod: updateMethod, closeAfter: closeAfter || this.shouldClose(), updateStates: updateStates, + planningUpdateMethods: planningUpdateMethods, }); } @@ -644,6 +648,7 @@ export class ItemManager { updateMethod = EVENTS.UPDATE_METHODS[0], closeAfter = false, updateStates = true, + planningUpdateMethods = {} } = {}) { if (!isEqual(this.state.errorMessages, [])) { return this.setState({ @@ -684,8 +689,16 @@ export class ItemManager { updates.pubstatus = POST_STATE.CANCELLED; } - if (this.props.itemType === ITEM_TYPE.EVENT) { + if (updates.type === 'event') { updates.update_method = updateMethod; + + if (Object.keys(planningUpdateMethods).length > 0) { + updates.associated_plannings?.forEach((planningItem) => { + if (planningUpdateMethods[planningItem._id] != null) { + planningItem.update_method = planningUpdateMethods[planningItem._id]; + } + }); + } } return promise.then(() => this.autoSave.flushAutosave()) diff --git a/client/components/Main/ItemEditor/tests/ItemManager_test.ts b/client/components/Main/ItemEditor/tests/ItemManager_test.ts index 2b83eba8b..d9d36b7b0 100644 --- a/client/components/Main/ItemEditor/tests/ItemManager_test.ts +++ b/client/components/Main/ItemEditor/tests/ItemManager_test.ts @@ -1391,6 +1391,7 @@ describe('components.Main.ItemManager', () => { updateMethod: EVENTS.UPDATE_METHODS[0], closeAfter: false, updateStates: true, + planningUpdateMethods: {}, }]); }); @@ -1404,6 +1405,7 @@ describe('components.Main.ItemManager', () => { updateMethod: EVENTS.UPDATE_METHODS[0], closeAfter: false, updateStates: true, + planningUpdateMethods: {}, }]); }); diff --git a/client/components/RelatedPlannings/PlanningMetaData/RelatedPlanningListItem.tsx b/client/components/RelatedPlannings/PlanningMetaData/RelatedPlanningListItem.tsx index 8a8fd19d4..9379184d0 100644 --- a/client/components/RelatedPlannings/PlanningMetaData/RelatedPlanningListItem.tsx +++ b/client/components/RelatedPlannings/PlanningMetaData/RelatedPlanningListItem.tsx @@ -1,21 +1,19 @@ import * as React from 'react'; import {connect} from 'react-redux'; -import {IPlanningItem, IG2ContentType, ILockedItems} from '../../../interfaces'; +import {IPlanningItem, IG2ContentType, ILockedItems, IAgenda} from '../../../interfaces'; import {IDesk, IUser} from 'superdesk-api'; import {superdeskApi} from '../../../superdeskApi'; -import {ICON_COLORS} from '../../../constants'; -import {planningUtils, lockUtils} from '../../../utils'; +import {lockUtils, getItemWorkflowStateLabel} from '../../../utils'; import * as selectors from '../../../selectors'; +import {Label} from 'superdesk-ui-framework/react'; import * as List from '../../UI/List'; -import {ItemIcon} from '../../ItemIcon'; import {AgendaNameList} from '../../Agendas'; -import {StateLabel} from '../../StateLabel'; import {CoverageIcons} from '../../Coverages/CoverageIcons'; -interface IProps { +interface IOwnProps { item: DeepPartial; active?: boolean; noBg?: boolean; @@ -23,21 +21,26 @@ interface IProps { showIcon?: boolean; shadow?: number; editPlanningComponent?: React.ReactNode; + isAgendaEnabled: boolean; onClick?(): void; +} - // Redux Store +interface IStateProps { users: Array; desks: Array; contentTypes: Array; lockedItems: ILockedItems; - isAgendaEnabled: boolean; + agendas: {[agendaId: string]: IAgenda}; } +type IProps = IOwnProps & IStateProps; + const mapStateToProps = (state) => ({ users: selectors.general.users(state), desks: selectors.general.desks(state), contentTypes: selectors.general.contentTypes(state), lockedItems: selectors.locks.getLockedItems(state), + agendas: selectors.general.agendasById(state), }); class RelatedPlanningListItemComponent extends React.PureComponent { @@ -47,74 +50,86 @@ class RelatedPlanningListItemComponent extends React.PureComponent { this.props.item, this.props.lockedItems ); + const stateLabel = getItemWorkflowStateLabel(this.props.item); + const agendas = (this.props.item.agendas ?? []) + .map((agendaId) => this.props.agendas[agendaId]) + .filter((agenda) => agenda != null); + const itemDescription = this.props.item.name || this.props.item.description_text || ''; return ( - - {!(this.props.showBorder && isItemLocked) ? null : ( - - )} -
- {!this.props.showIcon ? null : ( - - - - )} - + - - - {this.props.item.slugline} - - - {this.props.isAgendaEnabled && ( + {!(this.props.showBorder && isItemLocked) ? null : ( + + )} + - - {gettext('Agenda:')} - - + {this.props.showIcon !== true ? null : ( + + )} + {(this.props.item.slugline?.length ?? 0) === 0 ? null : ( + + {this.props.item.slugline} + )} + {itemDescription.length === 0 ? null : ( + + {this.props.item.name || this.props.item.description_text} + + )} + + + + + {this.props.editPlanningComponent == null ? null : ( + + {this.props.editPlanningComponent} + )} - - - - - - - - - - {!this.props.editPlanningComponent ? null : ( - - {this.props.editPlanningComponent} - - )} - + + ); } } -export const RelatedPlanningListItem = connect(mapStateToProps)(RelatedPlanningListItemComponent); +export const RelatedPlanningListItem = connect(mapStateToProps)(RelatedPlanningListItemComponent); diff --git a/client/components/fields/index.tsx b/client/components/fields/index.tsx index 431682865..be3b81eff 100644 --- a/client/components/fields/index.tsx +++ b/client/components/fields/index.tsx @@ -112,7 +112,7 @@ export function renderFieldsForPanel( profile: ISearchProfile, globalProps: {[key: string]: any}, fieldProps: {[key: string]: any}, - Container?: React.ComponentClass, + Container?: React.ComponentType, groupName?: string, enabledField: string = 'enabled', refs: {[key: string]: React.RefObject} = {}, @@ -182,7 +182,7 @@ export function renderGroupedFieldsForPanel( profile: ISearchProfile, globalProps: {[key: string]: any}, fieldProps: {[key: string]: any}, - Container?: React.ComponentClass, + Container?: React.ComponentType, enabledField: string = 'enabled' ) { const {gettext} = superdeskApi.localization; diff --git a/client/components/fields/preview/EventSchedule.tsx b/client/components/fields/preview/EventSchedule.tsx index ea0931191..2ec544d0f 100644 --- a/client/components/fields/preview/EventSchedule.tsx +++ b/client/components/fields/preview/EventSchedule.tsx @@ -5,7 +5,12 @@ import {TO_BE_CONFIRMED_FIELD} from '../../../constants'; import {EventScheduleSummary} from '../../Events/EventScheduleSummary'; -export class PreviewFieldEventSchedule extends React.PureComponent { +interface IProps extends IListFieldProps { + useFormLabelAndText?: boolean + addContentDivider?: boolean +} + +export class PreviewFieldEventSchedule extends React.PureComponent { render() { const item = this.props.item as IEventItem; @@ -15,6 +20,8 @@ export class PreviewFieldEventSchedule extends React.PureComponent ); } diff --git a/client/components/fields/preview/base/PreviewSimpleListItem.tsx b/client/components/fields/preview/base/PreviewSimpleListItem.tsx index 400df3601..74bcf7580 100644 --- a/client/components/fields/preview/base/PreviewSimpleListItem.tsx +++ b/client/components/fields/preview/base/PreviewSimpleListItem.tsx @@ -1,10 +1,16 @@ import * as React from 'react'; import {IBasePreviewProps} from './PreviewHoc'; import {stringUtils} from '../../../../utils'; +import {FormLabel, Text, ContentDivider} from 'superdesk-ui-framework/react'; -export class PreviewSimpleListItem extends React.PureComponent { +interface IProps extends IBasePreviewProps { + useFormLabelAndText?: boolean; + addContentDivider?: boolean; +} + +export class PreviewSimpleListItem extends React.PureComponent { render() { - if (this.props.value == undefined && !this.props.renderEmpty) { + if ((this.props.value?.length ?? 0) == 0 && this.props.renderEmpty !== true) { return null; } @@ -15,10 +21,24 @@ export class PreviewSimpleListItem extends React.PureComponent - {this.props.label} - {children} - + + {this.props.useFormLabelAndText ? ( +
+ + + {children} + +
+ ) : ( +
  • + {this.props.label} + {children} +
  • + )} + {this.props.addContentDivider !== true ? null : ( + + )} +
    ); } } diff --git a/client/components/fields/preview/index.ts b/client/components/fields/preview/index.ts index 0e697a419..4fa9b968c 100644 --- a/client/components/fields/preview/index.ts +++ b/client/components/fields/preview/index.ts @@ -302,9 +302,16 @@ Object.keys(multilingualFieldOptions).forEach((field) => { FIELD_TO_PREVIEW_COMPONENT.filter_schedule = PreviewFieldFilterSchedule; FIELD_TO_FORM_PREVIEW_COMPONENT.dates = PreviewFieldEventSchedule; +FIELD_TO_PREVIEW_COMPONENT.dates = PreviewFieldEventSchedule; + FIELD_TO_FORM_PREVIEW_COMPONENT.location = PreviewFieldLocation; + FIELD_TO_FORM_PREVIEW_COMPONENT.event_contact_info = PreviewFieldContacts; +FIELD_TO_PREVIEW_COMPONENT.event_contact_info = PreviewFieldContacts; + FIELD_TO_FORM_PREVIEW_COMPONENT.custom_vocabularies = PreviewFieldCustomVocabularies; +FIELD_TO_PREVIEW_COMPONENT.custom_vocabularies = PreviewFieldCustomVocabularies; + FIELD_TO_FORM_PREVIEW_COMPONENT.urgency = PreviewFieldUrgency; FIELD_TO_FORM_PREVIEW_COMPONENT.flags = PreviewFieldFlags; diff --git a/client/components/tests/IgnoreCancelSaveModal_test.tsx b/client/components/tests/IgnoreCancelSaveModal_test.tsx index 625559f08..2374ec030 100644 --- a/client/components/tests/IgnoreCancelSaveModal_test.tsx +++ b/client/components/tests/IgnoreCancelSaveModal_test.tsx @@ -1,18 +1,23 @@ import React from 'react'; +import {Provider} from 'react-redux'; import {mount} from 'enzyme'; import sinon from 'sinon'; import {cloneDeep} from 'lodash'; -import {eventUtils, generateTempId} from '../../utils'; +import {createTestStore, eventUtils, generateTempId} from '../../utils'; import * as testData from '../../utils/testData'; import * as helpers from './helpers'; +import {getTestActionStore} from '../../utils/testUtils'; import {IgnoreCancelSaveModalComponent} from '../IgnoreCancelSaveModal'; describe('', () => { + let astore; + let store; let wrapper; let handleHide; let item; + let updates; let itemType; let onCancel; let onIgnore; @@ -24,7 +29,9 @@ describe('', () => { let buttons; beforeEach(() => { + astore = getTestActionStore(); item = eventUtils.modifyForClient(cloneDeep(testData.events[0])); + updates = {}; itemType = item.type; handleHide = sinon.spy(); onCancel = sinon.spy(); @@ -33,26 +40,32 @@ describe('', () => { onGoTo = null; title = 'Test Modal'; autoClose = false; + + astore.init(); + store = createTestStore({initialState: astore.initialState}); }); const setWrapper = () => { wrapper = mount( - + + + ); resetButtons(); diff --git a/client/selectors/general.ts b/client/selectors/general.ts index 3023d705b..44cc867eb 100644 --- a/client/selectors/general.ts +++ b/client/selectors/general.ts @@ -1,5 +1,7 @@ import {get, keyBy} from 'lodash'; import {createSelector} from 'reselect'; + +import {IAgenda, IPlanningAppState} from '../interfaces'; import {getEnabledAgendas, getDisabledAgendas, getItemInArrayById} from '../utils'; import {ITEM_TYPE, COVERAGES, ASSIGNMENTS} from '../constants/index'; @@ -42,6 +44,21 @@ export const enabledAgendas = createSelector( [agendas], (agendas) => getEnabledAgendas(agendas) ); +export const agendasById = createSelector< + IPlanningAppState, + Array, + {[agendaId: string]: IAgenda} +>( + [agendas], + (agendas) => agendas.reduce( + (agendaList, agenda) => { + agendaList[agenda._id] = agenda; + + return agendaList; + }, + {} + ) +); export const disabledAgendas = createSelector( [agendas], diff --git a/client/utils/events.ts b/client/utils/events.ts index 43b52c09f..e49bf4aab 100644 --- a/client/utils/events.ts +++ b/client/utils/events.ts @@ -1357,11 +1357,13 @@ function convertCoverageToEventEmbedded(coverage: IPlanningCoverageItem): IEmbed return { coverage_id: coverage.coverage_id, g2_content_type: coverage.planning.g2_content_type, - desk: coverage.assigned_to.desk, - user: coverage.assigned_to.user, + desk: coverage.assigned_to?.desk, + user: coverage.assigned_to?.user, language: coverage.planning.language, - news_coverage_status: coverage.news_coverage_status.qcode, - scheduled: coverage.planning.scheduled, + news_coverage_status: coverage.news_coverage_status?.qcode ?? 'ncostat:int', + scheduled: coverage.planning.scheduled != null ? + moment(coverage.planning.scheduled).toDate() : + undefined, genre: coverage.planning.genre?.qcode, slugline: coverage.planning.slugline, headline: coverage.planning.headline, diff --git a/package-lock.json b/package-lock.json index 1f21f52e9..98fe562e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "superdesk-planning", - "version": "2.7.0-dev", + "version": "2.7.0-rc8", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -15,33 +15,33 @@ } }, "@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "dev": true }, "@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", "requires": { "regenerator-runtime": "^0.14.0" }, @@ -239,9 +239,9 @@ }, "dependencies": { "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true } } @@ -336,9 +336,9 @@ "dev": true }, "@types/node": { - "version": "20.12.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", - "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "dev": true, "requires": { "undici-types": "~5.26.4" @@ -481,10 +481,13 @@ "dev": true }, "semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -1179,9 +1182,9 @@ "dev": true }, "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", + "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "dev": true }, "axios": { @@ -1305,12 +1308,6 @@ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", "dev": true }, - "base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true - }, "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -1343,6 +1340,16 @@ "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", @@ -1383,6 +1390,12 @@ "unpipe": "1.0.0" }, "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -1391,6 +1404,15 @@ "requires": { "safer-buffer": ">= 2.1.2 < 3" } + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } } } }, @@ -1613,9 +1635,9 @@ "dev": true }, "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", "dev": true }, "cache-base": { @@ -1709,9 +1731,9 @@ } }, "caniuse-db": { - "version": "1.0.30001617", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001617.tgz", - "integrity": "sha512-JEfZYiroeTkXqNyFv8JJR0aN1tpTgBhst4UawTTKQ7ZVlAsMPWq1U8H9F/THtIXcV6u3CZCFEnkX+Fhgve8Z/w==", + "version": "1.0.30001633", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30001633.tgz", + "integrity": "sha512-Ba2XtmlCeKAixOIoWRcoXY0aKjKvdckV/yDVg5sVm4SIjyeFZgtCI4smJ9pLCC57bHUtw5VWhpjWGqES4k9h6A==", "dev": true }, "caseless": { @@ -1854,18 +1876,18 @@ }, "dependencies": { "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -1904,12 +1926,6 @@ "safe-buffer": "^5.0.1" } }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, "clap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", @@ -2211,14 +2227,6 @@ "on-headers": "~1.0.2", "safe-buffer": "5.1.2", "vary": "~1.1.2" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true - } } }, "concat-map": { @@ -2249,6 +2257,38 @@ "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" + }, + "dependencies": { + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + } } }, "connect-history-api-fallback": { @@ -2676,12 +2716,6 @@ "@babel/runtime": "^7.21.0" } }, - "date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", - "dev": true - }, "dateformat": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", @@ -3154,9 +3188,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.762", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.762.tgz", - "integrity": "sha512-rrFvGweLxPwwSwJOjIopy3Vr+J3cIPtZzuc74bmlvmBIgQO3VYJDvVrlj94iKZ3ukXUH64Ex31hSfRTLqvjYJQ==", + "version": "1.4.802", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.802.tgz", + "integrity": "sha512-TnTMUATbgNdPXVSHsxvNVSG0uEd6cSZsANjm8c9HbvflZVVn1yTRcmVXYT1Ma95/ssB/Dcd30AHweH2TE+dNpA==", "dev": true }, "elliptic": { @@ -3226,6 +3260,12 @@ "ws": "~8.11.0" }, "dependencies": { + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3861,9 +3901,9 @@ "dev": true }, "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", "dev": true }, "events": { @@ -3916,6 +3956,22 @@ "shebang-command": "^1.2.0", "which": "^1.2.9" } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true } } }, @@ -4059,27 +4115,21 @@ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - } - }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "dev": true }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4396,6 +4446,13 @@ "integrity": "sha512-0k45oWBokCqh2MOexeYKpyqmGKG+8mQ2Wd8iawx+uWd/weWJQAZ6SoPybagdCI4xFisag8iAR77WPm4h3pTfxA==", "dev": true }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -4424,35 +4481,18 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" - }, - "dependencies": { - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true - } } }, "find-up": { @@ -5624,14 +5664,6 @@ "requires": { "eventemitter3": "3.1.0", "url-toolkit": "^2.1.6" - }, - "dependencies": { - "eventemitter3": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", - "dev": true - } } }, "hmac-drbg": { @@ -5852,6 +5884,14 @@ "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" + }, + "dependencies": { + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + } } }, "http-proxy-middleware": { @@ -6900,19 +6940,34 @@ "yargs": "^16.1.1" }, "dependencies": { + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true + }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" + } + }, + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "requires": { + "ms": "2.1.2" } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -6924,6 +6979,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -6933,6 +6994,31 @@ "glob": "^7.1.3" } }, + "socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + } + }, + "socket.io-adapter": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "dev": true, + "requires": { + "debug": "~4.3.4", + "ws": "~8.11.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7355,6 +7441,12 @@ "streamroller": "^3.1.5" }, "dependencies": { + "date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -7430,13 +7522,12 @@ } }, "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "yallist": "^4.0.0" } }, "make-error": { @@ -7774,9 +7865,9 @@ "dev": true }, "nan": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", "dev": true }, "nanomatch": { @@ -8040,6 +8131,16 @@ "which": "^1.2.9" } }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -8054,6 +8155,12 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true } } }, @@ -9545,13 +9652,10 @@ "dev": true }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true }, "query-string": { "version": "4.3.4", @@ -9680,6 +9784,12 @@ "unpipe": "1.0.0" }, "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -10192,12 +10302,6 @@ "uuid": "^3.3.2" }, "dependencies": { - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true - }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -10593,16 +10697,6 @@ "y18n": "^3.2.1", "yargs-parser": "^5.0.1" } - }, - "yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } } } }, @@ -11118,65 +11212,6 @@ } } }, - "socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.5.2", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "socket.io-adapter": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", - "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", - "dev": true, - "requires": { - "debug": "~4.3.4", - "ws": "~8.11.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, "socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -11187,6 +11222,11 @@ "debug": "~4.3.1" }, "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -11196,6 +11236,11 @@ "ms": "2.1.2" } }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -11329,9 +11374,9 @@ } }, "spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", "dev": true }, "spdy": { @@ -11462,6 +11507,12 @@ "fs-extra": "^8.1.0" }, "dependencies": { + "date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -11712,6 +11763,12 @@ "integrity": "sha512-j/Toj7f1z98Hh2cYo2BVr85EpIRWqUi7rtRSGxh/cqUjqrnJe9l9UE7IUGd2vQ2p+kSHLkSzObQPZPLUC6TQwg==", "dev": true }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -11925,6 +11982,16 @@ "integrity": "sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==", "dev": true }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", @@ -12024,11 +12091,17 @@ "requires": { "mkdirp": "^0.5.1" } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true } } }, "superdesk-core": { - "version": "github:superdesk/superdesk-client-core#9ae1cec83428198cf0ef1552fef9af6098da2b4d", + "version": "github:superdesk/superdesk-client-core#61d411e5074fd6736693e67dcdf5b503d91198a8", "from": "github:superdesk/superdesk-client-core#develop", "dev": true, "requires": { @@ -12126,7 +12199,7 @@ "sass-loader": "6.0.6", "shortid": "2.2.8", "style-loader": "0.20.2", - "superdesk-ui-framework": "^3.1.3", + "superdesk-ui-framework": "^3.1.9", "ts-loader": "3.5.0", "typescript": "4.9.5", "uuid": "8.3.1", @@ -12212,9 +12285,9 @@ } }, "superdesk-ui-framework": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/superdesk-ui-framework/-/superdesk-ui-framework-3.1.5.tgz", - "integrity": "sha512-b+lJJGrf2vCyMabpUCngsbnINZQ6tGRZVjKfqZbPPge5jZVufOTDCM2aBCqj41AEEfVGM6r1BvZNQgClafRQ9g==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/superdesk-ui-framework/-/superdesk-ui-framework-3.1.9.tgz", + "integrity": "sha512-mgBkdsv/mvG02WUNt+7szbw+V0xDK0BtG5nuZag/GuvHjJmTGW0s0OhuC/Q+/SGkwg/iR+C7DLYqUY4l+7TQXQ==", "dev": true, "requires": { "@popperjs/core": "^2.4.0", @@ -12626,9 +12699,9 @@ "dev": true }, "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", "dev": true }, "type-check": { @@ -13245,6 +13318,7 @@ "dev": true, "optional": true, "requires": { + "bindings": "^1.5.0", "nan": "^2.12.1" } }, @@ -13743,6 +13817,7 @@ "dev": true, "optional": true, "requires": { + "bindings": "^1.5.0", "nan": "^2.12.1" } }, @@ -14082,9 +14157,9 @@ "dev": true }, "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "yargs": { @@ -14100,13 +14175,33 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" + }, + "dependencies": { + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + } } }, "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", + "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", + "dev": true + } + } }, "yn": { "version": "2.0.0", diff --git a/package.json b/package.json index a3f7ee398..813ffe0b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "superdesk-planning", - "version": "2.7.0-dev", + "version": "2.8.0-dev", "license": "AGPL-3.0", "description": "", "repository": { @@ -61,11 +61,14 @@ "karma-verbose-reporter": "0.0.6", "karma-webpack": "^2.0.13", "react-test-renderer": "16.2.0", + "redux": "^4.2.1", + "redux-logger": "^3.0.6", + "redux-thunk": "^2.4.2", "simulant": "^0.2.2", "sinon": "^4.5.0", "superdesk-code-style": "1.5.0", "superdesk-core": "github:superdesk/superdesk-client-core#develop", - "superdesk-ui-framework": "^3.0.76", + "superdesk-ui-framework": "^3.1.9", "ts-node": "~7.0.1", "tslint": "5.11.0", "typescript-eslint-parser": "^18.0.0" @@ -76,9 +79,9 @@ "moment": "^2.29.4", "moment-timezone": "^0.5.41", "react": "^16.9.0", - "redux": "^4.0.5", + "redux": "^4.2.1", "redux-logger": "^3.0.6", "redux-thunk": "^2.3.0", - "superdesk-ui-framework": "^3.0.71" + "superdesk-ui-framework": "*" } } diff --git a/server/planning/__init__.py b/server/planning/__init__.py index 45eda101f..58f960ad9 100644 --- a/server/planning/__init__.py +++ b/server/planning/__init__.py @@ -78,7 +78,7 @@ from planning.planning_locks import init_app as init_planning_locks_app from planning.search.planning_autocomplete import init_app as init_planning_autocomplete_app -__version__ = "2.7.0-dev" +__version__ = "2.8.0-dev" _SERVER_PATH = os.path.dirname(os.path.realpath(__file__)) diff --git a/server/planning/events/events.py b/server/planning/events/events.py index 18aff08f7..62b80fa02 100644 --- a/server/planning/events/events.py +++ b/server/planning/events/events.py @@ -103,6 +103,16 @@ def get_coverage_id(coverage: EmbeddedCoverageItem) -> str: ] +def is_event_updated(new_item: Event, old_item: Event) -> bool: + if new_item.get("name") != old_item.get("name"): + return True + new_subject = set([subject.get("qcode") for subject in new_item.get("subject", [])]) + old_subject = set([subject.get("qcode") for subject in old_item.get("subject", [])]) + if new_subject != old_subject: + return True + return False + + class EventsService(superdesk.Service): """Service class for the events model.""" @@ -130,7 +140,7 @@ def patch_in_mongo(self, id, document, original) -> Optional[Dict[str, Any]]: return response def is_new_version(self, new_item, old_item): - return is_new_version(new_item, old_item) + return is_new_version(new_item, old_item) or is_event_updated(new_item, old_item) def ingest_cancel(self, item, feeding_service): """Ignore cancelling on ingest, this will happen in ``update_post_item``""" diff --git a/server/planning/feed_parsers/onclusive.py b/server/planning/feed_parsers/onclusive.py index 31b38e482..a04fea917 100644 --- a/server/planning/feed_parsers/onclusive.py +++ b/server/planning/feed_parsers/onclusive.py @@ -75,6 +75,7 @@ def parse(self, content, provider=None): self.parse_event_details(event, item) self.parse_category(event, item) self.parse_contact_info(event, item) + self.set_expiry(item, provider) all_events.append(item) except EmbargoedException: logger.info("Ignoring embargoed event %s", event["itemId"]) @@ -262,14 +263,20 @@ def parse_contact_info(self, event, item): for contact_info in event.get("pressContacts"): item.setdefault("event_contact_info", []) contact_uri = "onclusive:{}".format(contact_info["pressContactID"]) - data = {"uri": contact_uri} + data = { + "uri": contact_uri, + "contact_email": [], + "contact_phone": [], + "organisation": "", + "first_name": "", + "last_name": "", + } + if contact_info.get("pressContactEmail"): - data.setdefault("contact_email", []).append(contact_info["pressContactEmail"]) + data["contact_email"].append(contact_info["pressContactEmail"]) if contact_info.get("pressContactTelephone"): - data.setdefault("contact_phone", []).append( - {"number": contact_info["pressContactTelephone"], "public": True} - ) + data["contact_phone"].append({"number": contact_info["pressContactTelephone"], "public": True}) if contact_info.get("pressContactOffice"): data["organisation"] = contact_info["pressContactOffice"] @@ -285,7 +292,6 @@ def parse_contact_info(self, event, item): existing_contact = get_resource_service("contacts").find_one(req=None, uri=contact_uri) if existing_contact is None: - logger.debug("New contact %s %s", contact_uri, data.get("organisation")) data.update( { "is_active": True, @@ -295,7 +301,14 @@ def parse_contact_info(self, event, item): get_resource_service("contacts").post([data]) item["event_contact_info"].append(bson.ObjectId(data["_id"])) else: - logger.debug("Existing contact %s %s", contact_uri, data.get("organisation")) existing_contact_id = bson.ObjectId(existing_contact["_id"]) get_resource_service("contacts").patch(existing_contact_id, data) item["event_contact_info"].append(existing_contact_id) + + def set_expiry(self, event, provider) -> None: + expiry_minutes = ( + int(provider.get("content_expiry") if provider else 0) + or int(app.config.get("INGEST_EXPIRY_MINUTES", 0)) + or (60 * 24) + ) + event["expiry"] = event["dates"]["end"] + datetime.timedelta(minutes=(expiry_minutes)) diff --git a/server/planning/feed_parsers/onclusive_tests.py b/server/planning/feed_parsers/onclusive_tests.py index 2d1cc5dae..79918b778 100644 --- a/server/planning/feed_parsers/onclusive_tests.py +++ b/server/planning/feed_parsers/onclusive_tests.py @@ -97,17 +97,23 @@ def test_content(self): data = deepcopy(self.data) data["pressContacts"][0]["pressContactEmail"] = "foo@example.com" + data["pressContacts"][0].pop("pressContactTelephone") + data["pressContacts"][0]["pressContactName"] = "Foo Bar" item = OnclusiveFeedParser().parse([data])[0] self.assertIsInstance(item["event_contact_info"][0], bson.ObjectId) contact = superdesk.get_resource_service("contacts").find_one(req=None, _id=item["event_contact_info"][0]) self.assertEqual(1, superdesk.get_resource_service("contacts").find({}).count()) self.assertEqual(["foo@example.com"], contact["contact_email"]) + self.assertEqual([], contact["contact_phone"]) + self.assertEqual("Foo", contact["first_name"]) self.assertEqual(item["occur_status"]["qcode"], "eocstat:eos5") - [data][0]["isProvisional"] = True + data["isProvisional"] = True item = OnclusiveFeedParser().parse([data])[0] self.assertEqual(item["occur_status"]["qcode"], "eocstat:eos3") + self.assertGreater(item["expiry"], item["dates"]["end"]) + def test_content_no_time(self): data = self.data.copy() data["time"] = "" diff --git a/server/planning/feeding_services/onclusive_api_service.py b/server/planning/feeding_services/onclusive_api_service.py index 81e300cc6..21131408f 100644 --- a/server/planning/feeding_services/onclusive_api_service.py +++ b/server/planning/feeding_services/onclusive_api_service.py @@ -109,8 +109,6 @@ def _update(self, provider, update): update["tokens"]["import_finished"] = None update["tokens"]["date"] = "" - reingesting = update["tokens"].get("reingesting") - if update["tokens"].get("import_finished"): # populate it for cases when import was done before we introduced the field update["tokens"].setdefault("next_start", update["tokens"]["import_finished"] - timedelta(hours=5)) @@ -157,15 +155,12 @@ def _update(self, provider, update): logger.info("Onclusive returned %d items", len(items)) for item in items: item.setdefault("language", self.language) - if reingesting: - item["versioncreated"] += timedelta(seconds=1) # bump versioncreated to trigger an update if items: yield items update["tokens"][iterations_param] = i else: # there was no break so we are done update["tokens"]["import_finished"] = utcnow() - update["tokens"]["reingesting"] = False except SoftTimeLimitExceeded: logger.warning("stopped due to time limit, tokens=%s", update["tokens"]) diff --git a/server/planning/feeding_services/onclusive_api_service_tests.py b/server/planning/feeding_services/onclusive_api_service_tests.py index 22a1a03f0..c2281cf61 100644 --- a/server/planning/feeding_services/onclusive_api_service_tests.py +++ b/server/planning/feeding_services/onclusive_api_service_tests.py @@ -105,6 +105,4 @@ def test_reingest(self, lock_touch): items = list(self.service._update(self.provider, updates)) assert 10 == len(items) assert 1 == len(items[0]) - assert items[0][0]["versioncreated"].isoformat() > self.event["versioncreated"] assert updates["tokens"]["import_finished"] - assert not updates["tokens"]["reingesting"] diff --git a/server/planning/tests/events_service_test.py b/server/planning/tests/events_service_test.py new file mode 100644 index 000000000..daa72b041 --- /dev/null +++ b/server/planning/tests/events_service_test.py @@ -0,0 +1,31 @@ +from datetime import datetime, timedelta + +from planning.events.events import EventsService +from planning.types import Event + + +def test_is_new_version(): + service = EventsService() + + new_event: Event = {"versioncreated": datetime.now()} + old_event: Event = {"versioncreated": datetime.now() - timedelta(days=1)} + + assert service.is_new_version(new_event, old_event) + + new_event = {"versioncreated": datetime.now()} + old_event = new_event.copy() + + assert not service.is_new_version(new_event, old_event) + + new_event["subject"] = [{"qcode": "foo"}] + old_event["subject"] = [{"qcode": "bar"}] + + assert service.is_new_version(new_event, old_event) + + +def test_should_update(): + service = EventsService() + new_event: Event = {"versioncreated": datetime.now()} + old_event: Event = new_event.copy() + + assert service.should_update(old_event, new_event, provider={}) diff --git a/setup.py b/setup.py index a1c6da75d..eb27bb628 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name="superdesk-planning", - version="2.7.0-dev", + version="2.8.0-dev0", description=DESCRIPTION, long_description=DESCRIPTION, package_dir={"": "server"},