From b4b642193753901537e3fe873cf3c7a8d5f24906 Mon Sep 17 00:00:00 2001 From: MarkLark86 Date: Tue, 24 Oct 2023 15:59:39 +1100 Subject: [PATCH] Merge hotfix 2.6.3 develop (#1868) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [SDAAP-64] Fix null location crash in notifications (#1773) * [SDESK-6873] fix: Backport moment-timezone upgrade * fix(client): Change moment import to moment-timezone * fix lint errors * [SDNTB-804] feature: Add priority to Events, Planning and Coverages (#1772) * ui: PlanningAPI updates * api: Add priority to ContentProfiles * ui: Add priority to ContentProfiles * api/ui: Add priority to search * ui: Default values * ui: fix unit tests * api: Add behave tests for search * fix: Getting profiles by ID not working when using system default profiles * change limit when calling onclusive api (#1783) * change limit when calling onclusive api use 1000 instead of 100 to do less pagination, also fetch some extra items to avoid possible gaps. SDCP-684 * fix date filtering for all day events (#1782) * fix date filtering for all day events when making a query also do a query for all_day events using only date part of the given date, but it must be first converted to local time using client timezone so it will the date user sees in the UI. also tweak the way the all_day events are assigned to dates in the UI based on newsroom code. SDCP-682 * avoid dynamic mapping for event calendars (#1784) SDESK-6876 * [SDNTB-804] fix(ui): Load ContentProfiles using its name not _id (#1785) * fix onclusive ingest (#1786) - keep timestamp for next run based on current start - look for timezone not only using name but also offset - increase buffer when getting updates (not clear if the timestamps should be utc or local, so add some extra time - for events with no end time set it to start time and not some fake end time which might make it go visible on a next day SDCP-688 SDCP-690 * check all timezones when parsing onclusive (#1787) SDCP-688 * fix events not visible in the UI (#1790) mostly related to events with `no_end_time` flag which have same end timestamp as start one. SDCP-680 * there was an error in timezone detection (#1792) when the event started during the hour when there was daylight savings switch for a timezone, so rather work with utc date. SDCP-688 * fix event date not visible in preview (#1791) * fix event date not visible in preview that was the case for events with no end time and same timezone as local one for user. SDCP-690 * add missing end date for multi day events with no end time * Fix : Some events ingested from Onclusive do not contain the complete location info [SDCP-692] (#1794) * fix : Some events ingested from Onclusive do not contain the complete location info [SDCP-692] * update testcases * parse location.location field * fix black * update logic * set language for onclusive events based on product (#1796) SDCP-696 * fix timestamps from onclusive (#1797) those are currently using London timezone, not utc. * use languages with -CA for onclusive events (#1798) so it's consistent with manually created events SDCP-696 * use /date api for initial import from onclusive (#1801) use `/date` api instead of `/between` and log imported events into a text file so we can check that if needed. SDCP-699 * [SDESK-6829] fix(ingest_rule): Skip autopost action if item is already posted (#1802) Multiple posts will still occur if a Calendar or Agenda is to be added in the ingest rule (due to the separation of ingest, and executing routing rules) * fix server requirements (#1804) * fix server requirements * fix e2e core server version * release 2.6.2 * [SDESK-6972] fix(api): Allow Event dates.tz to have a null value (#1816) * fix(api): Allow dates.tz to have a null value * Add tests * Update UIF (#1851) * fix switch label param (#1855) * update server requirements * log each event id we process from onclusive (#1864) so we can check logs in case some event is missing CPCN-380 * fix missed conflict in package.json * fix: add default values to util functions * add missing import * fix merge issues * fix default planning language value * fix(e2e): Select tree popover from body not dom parent * fix(unit tests): Store utcnow before checking versionposted --------- Co-authored-by: Andrew Marwood Co-authored-by: Petr JaĊĦek Co-authored-by: devketanpro <73937490+devketanpro@users.noreply.github.com> Co-authored-by: Konstantin Markov --- .github/workflows/lint-server.yml | 6 + client/actions/agenda.ts | 14 +- client/actions/events/ui.ts | 8 +- client/actions/tests/agenda_test.ts | 10 +- client/api/combined.ts | 1 + client/api/contentProfiles.ts | 49 ++ client/api/events.ts | 3 +- client/api/planning.ts | 3 +- client/api/search.ts | 4 +- client/components/AdvancedSearch/index.tsx | 4 + .../ContentProfiles/FieldTab/FieldEditor.tsx | 2 + .../Coverages/CoverageEditor/CoverageForm.tsx | 1 + client/components/Events/EventDateTime.tsx | 5 +- .../Events/EventScheduleSummary/index.tsx | 3 +- .../editor/ProfileFieldDefaultValue.tsx | 27 + .../fields/editor/base/numberSelect.tsx | 125 +++ client/components/fields/index.tsx | 4 + client/components/fields/preview/index.ts | 4 + client/components/fields/resources/common.ts | 17 + .../components/fields/resources/profiles.ts | 12 + client/interfaces.ts | 13 +- client/planning-extension/package-lock.json | 819 ++++++++++++------ client/selectors/vocabs.ts | 31 +- client/utils/contentProfiles.ts | 2 + client/utils/events.ts | 207 +++-- client/utils/index.ts | 1 + client/utils/planning.ts | 104 ++- client/utils/search.ts | 5 +- client/utils/tests/planning_test.ts | 24 +- client/validators/index.ts | 19 +- e2e/cypress/e2e/events/edit_event.cy.ts | 67 +- .../events/event_action_create_planning.cy.ts | 15 +- e2e/cypress/e2e/search/search_combined.cy.ts | 4 +- e2e/cypress/e2e/search/search_events.cy.ts | 4 +- e2e/cypress/e2e/search/search_filters.cy.ts | 12 +- e2e/cypress/e2e/search/search_planning.cy.ts | 4 +- e2e/cypress/fixtures/events.ts | 32 +- e2e/cypress/fixtures/planning.ts | 14 +- .../support/common/inputs/treeSelect.ts | 6 +- e2e/cypress/support/utils/time.ts | 14 +- e2e/package.json | 4 +- e2e/server/gunicorn_config.py | 3 +- package-lock.json | 89 +- package.json | 10 +- server/features/events.feature | 30 +- server/features/search_events.feature | 16 +- server/features/search_planning.feature | 14 +- .../content_profiles/profiles/coverage.py | 2 + .../content_profiles/profiles/event.py | 6 + .../content_profiles/profiles/planning.py | 2 + server/planning/events/events.py | 4 +- server/planning/events/events_schema.py | 7 +- server/planning/events/events_tests.py | 3 +- server/planning/feed_parsers/onclusive.py | 110 ++- .../feed_parsers/onclusive_sample.json | 10 +- .../planning/feed_parsers/onclusive_tests.py | 65 +- .../feeding_services/onclusive_api_service.py | 78 +- .../onclusive_api_service_tests.py | 19 +- server/planning/io/ingest_rule_handler.py | 16 +- server/planning/planning/planning.py | 1 + .../planning/search/eventsplanning_filters.py | 1 + server/planning/search/queries/common.py | 26 +- server/planning/search/queries/elastic.py | 39 +- server/planning/search/queries/events.py | 41 +- server/planning/search/queries/planning.py | 10 +- 65 files changed, 1626 insertions(+), 679 deletions(-) create mode 100644 client/components/fields/editor/ProfileFieldDefaultValue.tsx create mode 100644 client/components/fields/editor/base/numberSelect.tsx diff --git a/.github/workflows/lint-server.yml b/.github/workflows/lint-server.yml index 3d023aa37..02ec7a412 100644 --- a/.github/workflows/lint-server.yml +++ b/.github/workflows/lint-server.yml @@ -8,6 +8,8 @@ jobs: steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 + with: + python-version: '3.10' - run: pip install black~=23.0 - run: black --check server @@ -16,6 +18,8 @@ jobs: steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 + with: + python-version: '3.10' - run: pip install flake8 - run: flake8 server @@ -24,5 +28,7 @@ jobs: steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 + with: + python-version: '3.10' - run: pip install -Ur server/mypy-requirements.txt - run: mypy server diff --git a/client/actions/agenda.ts b/client/actions/agenda.ts index 05c048c13..e2157511b 100644 --- a/client/actions/agenda.ts +++ b/client/actions/agenda.ts @@ -3,6 +3,7 @@ import {cloneDeep, pick, get, sortBy, findIndex} from 'lodash'; import {Moment} from 'moment'; import {IEventItem, IPlanningItem, IAgenda} from '../interfaces'; +import {planningApi} from '../superdeskApi'; import {AGENDA, MODALS, EVENTS} from '../constants'; import {getErrorMessage, gettext, planningUtils} from '../utils'; @@ -240,15 +241,20 @@ const addEventToCurrentAgenda = ( ); export function convertEventToPlanningItem(event: IEventItem): Partial { + const defaultPlace = selectors.general.defaultPlaceList(planningApi.redux.store.getState()); + const defaultValues = planningUtils.defaultPlanningValues(null, defaultPlace); + let newPlanningItem: Partial = { + ...defaultValues, type: 'planning', event_item: event._id, planning_date: event._sortDate || event.dates?.start, - place: event.place, + place: event.place || defaultPlace, subject: event.subject, anpa_category: event.anpa_category, agendas: [], - language: event.language, + language: event.language || defaultValues.language, + languages: event.languages || defaultValues.languages, }; newPlanningItem = convertStringFields( @@ -269,6 +275,10 @@ export function convertEventToPlanningItem(event: IEventItem): Partial ({ */ const createEventFromPlanning = (plan: IPlanningItem) => ( (dispatch, getState) => { - const defaultDurationOnChange = selectors.forms.defaultEventDuration(getState()); - const occurStatuses = selectors.vocabs.eventOccurStatuses(getState()); + const state = getState(); + const defaultDurationOnChange = selectors.forms.defaultEventDuration(state); + const occurStatuses = selectors.vocabs.eventOccurStatuses(state); + const defaultCalendar = selectors.events.defaultCalendarValue(state); + const defaultPlace = selectors.general.defaultPlaceList(state); const unplannedStatus = getItemInArrayById(occurStatuses, 'eocstat:eos0', 'qcode') || { label: 'Unplanned event', qcode: 'eocstat:eos0', @@ -750,6 +753,7 @@ const createEventFromPlanning = (plan: IPlanningItem) => ( }; const eventProfile = selectors.forms.eventProfile(getState()); let newEvent: Partial = { + ...eventUtils.defaultEventValues(occurStatuses, defaultCalendar, defaultPlace), dates: { start: moment(plan.planning_date).clone(), end: moment(plan.planning_date) diff --git a/client/actions/tests/agenda_test.ts b/client/actions/tests/agenda_test.ts index 1e9d25fd4..addd07b89 100644 --- a/client/actions/tests/agenda_test.ts +++ b/client/actions/tests/agenda_test.ts @@ -327,6 +327,15 @@ describe('agenda', () => { {}, { type: 'planning', + state: 'draft', + item_class: 'plinat:newscoverage', + language: 'en', + languages: ['en'], + flags: { + marked_for_not_publication: false, + overide_auto_assign_to_workflow: false, + }, + coverages: [], event_item: events[0]._id, planning_date: events[0].dates.start, slugline: events[0].slugline, @@ -345,7 +354,6 @@ describe('agenda', () => { }], internal_note: 'internal note', ednote: 'Editorial note about this Event', - language: undefined, }, ]); diff --git a/client/api/combined.ts b/client/api/combined.ts index e4f91a2c8..cca8ece5f 100644 --- a/client/api/combined.ts +++ b/client/api/combined.ts @@ -26,6 +26,7 @@ function convertCombinedParams(params: ISearchParams): Partial include_associated_planning: params.include_associated_planning, source: cvsToString(params.source, 'id'), coverage_user_id: params.coverage_user_id, + priority: arrayToString(params.priority), }; } diff --git a/client/api/contentProfiles.ts b/client/api/contentProfiles.ts index 3ad59d078..6136ce23c 100644 --- a/client/api/contentProfiles.ts +++ b/client/api/contentProfiles.ts @@ -4,6 +4,7 @@ import { IPlanningContentProfile, IPlanningAPI, IEventOrPlanningItem, + IPlanningCoverageItem, IProfileMultilingualDetails, IProfileSchemaTypeString, } from '../interfaces'; @@ -30,11 +31,45 @@ function getAll(): Promise> { ) .then((response) => { response._items.forEach(sortProfileGroups); + enablePriorityInSearchProfile(response._items); return response._items; }); } +function enablePriorityInSearchProfile(profiles: Array) { + // Hack to enable/disable priority field in search profiles based on the content profiles + // TODO: Remove this hack when we implement a solution for all searchable fields + const profilesById: {[id: string]: IPlanningContentProfile} = profiles.reduce((profileMap, profile) => { + profileMap[profile.name] = profile; + + return profileMap; + }, {}); + const searchProfile = profilesById.advanced_search.editor; + const priorityEnabled = { + event: profilesById.event.editor.priority?.enabled === true, + planning: profilesById.planning.editor.priority?.enabled === true, + }; + + const priorityField = { + enabled: true, + index: 5, + group: 'common', + search_enabled: true, + filter_enabled: true, + }; + + if (priorityEnabled.event) { + searchProfile.event.priority = priorityField; + if (priorityEnabled.planning) { + searchProfile.combined.priority = priorityField; + } + } + if (priorityEnabled.planning) { + searchProfile.planning.priority = priorityField; + } +} + function getProfile(contentType: string): IPlanningContentProfile { const {getState} = planningApi.redux.store; @@ -192,9 +227,23 @@ function updateProfilesInStore(): Promise { }); } +function getDefaultValues(profile: IPlanningContentProfile): DeepPartial { + return Object.keys(profile?.schema ?? {}).reduce( + (defaults, field) => { + if (profile.schema[field]?.default_value != null) { + defaults[field] = profile.schema[field].default_value; + } + + return defaults; + }, + {} + ); +} + export const contentProfiles: IPlanningAPI['contentProfiles'] = { getAll: getAll, get: getProfile, + getDefaultValues: getDefaultValues, patch: patch, showManagePlanningProfileModal: showManagePlanningProfileModal, showManageEventProfileModal: showManageEventProfileModal, diff --git a/client/api/events.ts b/client/api/events.ts index 4d14a5b96..266d4cd0b 100644 --- a/client/api/events.ts +++ b/client/api/events.ts @@ -11,7 +11,7 @@ import {IRestApiResponse} from 'superdesk-api'; import {planningApi, superdeskApi} from '../superdeskApi'; import {EVENTS, TEMP_ID_PREFIX} from '../constants'; -import {convertCommonParams, cvsToString, searchRaw, searchRawGetAll} from './search'; +import {arrayToString, convertCommonParams, cvsToString, searchRaw, searchRawGetAll} from './search'; import {eventUtils, planningUtils} from '../utils'; import {eventProfile, eventSearchProfile} from '../selectors/forms'; import * as actions from '../actions'; @@ -23,6 +23,7 @@ function convertEventParams(params: ISearchParams): Partial { location: params.location?.qcode, calendars: cvsToString(params.calendars), no_calendar_assigned: params.no_calendar_assigned, + priority: arrayToString(params.priority), }; } diff --git a/client/api/planning.ts b/client/api/planning.ts index ff222b203..845539c55 100644 --- a/client/api/planning.ts +++ b/client/api/planning.ts @@ -34,7 +34,8 @@ function convertPlanningParams(params: ISearchParams): Partial g2_content_type: params.g2_content_type?.qcode, source: cvsToString(params.source, 'id'), coverage_user_id: params.coverage_user_id, - coverage_assignment_status: params.coverage_assignment_status + coverage_assignment_status: params.coverage_assignment_status, + priority: arrayToString(params.priority), }; } diff --git a/client/api/search.ts b/client/api/search.ts index 50bcbaa98..66cd2b1e3 100644 --- a/client/api/search.ts +++ b/client/api/search.ts @@ -2,6 +2,7 @@ import {ISearchAPIParams, ISearchParams} from '../interfaces'; import {superdeskApi} from '../superdeskApi'; import {IRestApiResponse} from 'superdesk-api'; import {getDateTimeElasticFormat, getTimeZoneOffset} from '../utils'; +import {default as timeUtils} from '../utils/time'; export function cvsToString(items?: Array<{[key: string]: any}>, field: string = 'qcode'): string { @@ -11,7 +12,7 @@ export function cvsToString(items?: Array<{[key: string]: any}>, field: string = ); } -export function arrayToString(items?: Array): string { +export function arrayToString(items?: Array): string { return (items ?? []) .join(','); } @@ -48,6 +49,7 @@ export function convertCommonParams(params: ISearchParams): Partial { location: { disableAddLocation: false, }, + priority: { + multiple: true, + defaultValue: [], + }, }, null, this.props.enabledField diff --git a/client/components/ContentProfiles/FieldTab/FieldEditor.tsx b/client/components/ContentProfiles/FieldTab/FieldEditor.tsx index 0f29fa374..bc5857e2d 100644 --- a/client/components/ContentProfiles/FieldTab/FieldEditor.tsx +++ b/client/components/ContentProfiles/FieldTab/FieldEditor.tsx @@ -105,6 +105,7 @@ export class FieldEditor extends React.Component { !['language', 'location'].includes(this.props.item.name) ) )}, + 'schema.default_value': {enabled: this.props.item.name === 'priority'}, }; const noOptionsAvailable = !( Object.values(fieldProps) @@ -189,6 +190,7 @@ export class FieldEditor extends React.Component { 'schema.languages': {enabled: true, index: 12}, 'schema.default_language': {enabled: true, index: 13}, 'schema.planning_auto_publish': {enabled: true, index: 14}, + 'schema.default_value': {enabled: true, index: 11}, }, { item: this.props.item, diff --git a/client/components/Coverages/CoverageEditor/CoverageForm.tsx b/client/components/Coverages/CoverageEditor/CoverageForm.tsx index b3955ed8d..135a7b13a 100644 --- a/client/components/Coverages/CoverageEditor/CoverageForm.tsx +++ b/client/components/Coverages/CoverageEditor/CoverageForm.tsx @@ -449,6 +449,7 @@ export class CoverageFormComponent extends React.Component { this.props.value.planning?.g2_content_type === 'text' ), }, + priority: {field: 'planning.priority'}, }; const profile = editor.item.planning.getCoverageFields(); diff --git a/client/components/Events/EventDateTime.tsx b/client/components/Events/EventDateTime.tsx index 8730fa082..3673d0b2e 100644 --- a/client/components/Events/EventDateTime.tsx +++ b/client/components/Events/EventDateTime.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import moment from 'moment'; import {superdeskApi} from '../../superdeskApi'; import {IEventItem} from '../../interfaces'; @@ -20,8 +19,8 @@ export class EventDateTime extends React.PureComponent { render() { const {gettext} = superdeskApi.localization; const {item, ignoreAllDay, displayLocalTimezone} = this.props; - const start = moment(item.dates.start); - const end = moment(item.dates.end); + const start = eventUtils.getStartDate(item); + const end = eventUtils.getEndDate(item); const isAllDay = eventUtils.isEventAllDay(start, end); const multiDay = !eventUtils.isEventSameDay(start, end); const isRemoteTimeZone = timeUtils.isEventInDifferentTimeZone(item); diff --git a/client/components/Events/EventScheduleSummary/index.tsx b/client/components/Events/EventScheduleSummary/index.tsx index 75a7779f5..eb8e6ee59 100644 --- a/client/components/Events/EventScheduleSummary/index.tsx +++ b/client/components/Events/EventScheduleSummary/index.tsx @@ -20,8 +20,9 @@ export const EventScheduleSummary = ({ forUpdating = false, useEventTimezone = false }: IProps) => { - if (!event) + if (!event) { return null; + } const eventSchedule: IEventItem['dates'] = get(event, 'dates', {}); const doesRepeat = get(eventSchedule, 'recurring_rule', null) !== null; diff --git a/client/components/fields/editor/ProfileFieldDefaultValue.tsx b/client/components/fields/editor/ProfileFieldDefaultValue.tsx new file mode 100644 index 000000000..4ec5acc2a --- /dev/null +++ b/client/components/fields/editor/ProfileFieldDefaultValue.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; + +import {IEditorFieldProps, IProfileFieldEntry} from '../../../interfaces'; + +import {renderFieldsForPanel} from '../index'; + +interface IProps extends IEditorFieldProps { + item: IProfileFieldEntry; + onChange(field: string, value: string | number): void; +} + +export function ProfileFieldDefaultValue({item, onChange, ...props}: IProps) { + return renderFieldsForPanel( + 'editor', + {[item.name]: {enabled: true, index: 1}}, + { + item: item, + onChange: onChange, + }, + { + [item.name]: { + ...props, + field: 'schema.default_value', + }, + } + ); +} diff --git a/client/components/fields/editor/base/numberSelect.tsx b/client/components/fields/editor/base/numberSelect.tsx new file mode 100644 index 000000000..8a876378d --- /dev/null +++ b/client/components/fields/editor/base/numberSelect.tsx @@ -0,0 +1,125 @@ +import * as React from 'react'; +import {get} from 'lodash'; + +import {superdeskApi} from '../../../../superdeskApi'; +import {Select, Option, TreeSelect} from 'superdesk-ui-framework/react'; +import {IEditorFieldProps} from '../../../../interfaces'; + +import {Row} from '../../../UI/Form'; + +interface IPropsBase extends IEditorFieldProps { + options: Array; + clearable?: boolean; + readOnly?: boolean; + info?: string; +} + +interface IPropsSingle extends IPropsBase { + multiple: false; + defaultValue?: number; + onChange(field: string, value: number): void; +} + +interface IPropsMultiple extends IPropsBase { + multiple: true; + defaultValue?: Array; + onChange(field: string, value: Array): void; +} + +type IProps = IPropsSingle | IPropsMultiple; + + +export class EditorFieldNumberSelect extends React.PureComponent { + node: React.RefObject; + + constructor(props) { + super(props); + + this.node = React.createRef(); + this.onChangeSingle = this.onChangeSingle.bind(this); + this.onChangeMultiple = this.onChangeMultiple.bind(this); + } + + onChangeSingle(newValue: string) { + if (this.props.multiple === false) { + this.props.onChange(this.props.field, parseInt(newValue, 10)); + } + } + + onChangeMultiple(newValue: Array) { + if (this.props.multiple === true) { + this.props.onChange(this.props.field, newValue); + } + } + + focus() { + if (this.node.current != null) { + this.node.current.getElementsByTagName('select')[0]?.focus(); + } + } + + renderSingle(value: number) { + const {gettext} = superdeskApi.localization; + const error = get(this.props.errors ?? {}, this.props.field); + + return ( + + ); + } + + renderMultiple(values: Array) { + return ( + this.props.options.map((value) => ({ + value: value, + }))} + getLabel={(item) => item.toString(10)} + getId={(item) => item.toString(10)} + value={values} + onChange={this.onChangeMultiple} + allowMultiple={true} + /> + ); + } + + render() { + const value = get(this.props.item, this.props.field, this.props.defaultValue); + + return ( + + {this.props.multiple === false ? + this.renderSingle(value) : + this.renderMultiple(value) + } + + ); + } +} diff --git a/client/components/fields/index.tsx b/client/components/fields/index.tsx index 342a7c811..357d9c32b 100644 --- a/client/components/fields/index.tsx +++ b/client/components/fields/index.tsx @@ -261,6 +261,7 @@ const PREVIEW_GROUPS: IPreviewGroups = { 'language', 'slugline', 'name', + 'priority', 'definition_short', 'occur_status', 'dates', @@ -292,6 +293,7 @@ const PREVIEW_GROUPS: IPreviewGroups = { 'headline', 'name', 'planning_date', + 'priority', 'description_text', 'internal_note', 'place', @@ -313,6 +315,7 @@ const PREVIEW_GROUPS: IPreviewGroups = { fields: [ 'language', 'slugline', + 'priority', 'ednote', 'keyword', 'internal_note', @@ -330,6 +333,7 @@ const PREVIEW_GROUPS: IPreviewGroups = { 'dates', 'location', 'occur_status', + 'priority', 'definition_short', 'event_contact_info', ], diff --git a/client/components/fields/preview/index.ts b/client/components/fields/preview/index.ts index eafda25db..15d3e207f 100644 --- a/client/components/fields/preview/index.ts +++ b/client/components/fields/preview/index.ts @@ -275,6 +275,10 @@ const multilingualFieldOptions: {[key: string]: IPreviewHocOptions} = { props: () => ({label: superdeskApi.localization.gettext('Accreditation Info')}), getValue: getPreviewString, }, + priority: { + props: () => ({label: superdeskApi.localization.gettext('Priority:')}), + getValue: getPreviewString, + }, }; let FIELD_TO_PREVIEW_COMPONENT: {[key: string]: any} = {}; diff --git a/client/components/fields/resources/common.ts b/client/components/fields/resources/common.ts index 84f26d41c..16511d0d5 100644 --- a/client/components/fields/resources/common.ts +++ b/client/components/fields/resources/common.ts @@ -2,7 +2,10 @@ import {registerEditorField} from './registerEditorFields'; import {superdeskApi} from '../../../superdeskApi'; +import {getPriorityQcodes} from '../../../selectors/vocabs'; + import {EditorFieldMultilingualText} from '../editor/base/multilingualText'; +import {EditorFieldNumberSelect} from '../editor/base/numberSelect'; import {EditorFieldEventAttachments} from '../editor/EventAttachments'; registerEditorField( @@ -59,3 +62,17 @@ registerEditorField( null, false ); + +registerEditorField( + 'priority', + EditorFieldNumberSelect, + (props) => ({ + label: superdeskApi.localization.gettext('Priority'), + field: 'priority', + multiple: false, + }), + (state) => ({ + options: getPriorityQcodes(state), + }), + false +); diff --git a/client/components/fields/resources/profiles.ts b/client/components/fields/resources/profiles.ts index c45e62830..32aaa2397 100644 --- a/client/components/fields/resources/profiles.ts +++ b/client/components/fields/resources/profiles.ts @@ -9,6 +9,7 @@ import {EditorFieldSelect} from '../editor/base/select'; import {EditorFieldCheckbox} from '../editor/base/checkbox'; import {EditorFieldTreeSelect, IEditorFieldTreeSelectProps} from '../editor/base/treeSelect'; import {SelectCustomVocabulariesList} from '../editor/SelectCustomVocabulariesList'; +import {ProfileFieldDefaultValue} from '../editor/ProfileFieldDefaultValue'; import {getLanguagesForTreeSelectInput} from '../../../selectors/vocabs'; @@ -188,3 +189,14 @@ registerEditorField ({ + label: superdeskApi.localization.gettext('Default Value'), + field: 'schema.default_value', + }), + null, + true +); diff --git a/client/interfaces.ts b/client/interfaces.ts index 13d159f3f..b84ad5acf 100644 --- a/client/interfaces.ts +++ b/client/interfaces.ts @@ -426,6 +426,7 @@ export interface IEventItem extends IBaseRestApiResponse { event_created?: string | Date; event_lastmodified?: string | Date; name?: string; + priority?: number; definition_short?: string; definition_long?: string; internal_note?: string; @@ -597,6 +598,7 @@ export interface ICoveragePlanningDetails { slugline: string; internal_note: string; workflow_status_reason: string; + priority?: number; } export interface ICoverageScheduledUpdate { @@ -899,6 +901,7 @@ export interface ICommonAdvancedSearchParams { id?: string; name?: string; }>; + priority?: Array; } export interface ICommonSearchParams { @@ -978,6 +981,7 @@ interface IBaseProfileSchemaType { validate_on_post?: boolean; minlength?: number; maxlength?: number; + default_value?: string | number; } export interface IProfileSchemaTypeList extends IBaseProfileSchemaType<'list'> { @@ -1330,6 +1334,7 @@ export interface ISearchParams { item_ids?: Array; name?: string; tz_offset?: string; + time_zone?: string; full_text?: string; anpa_category?: Array; subject?: Array; @@ -1359,6 +1364,8 @@ export interface ISearchParams { name?: string; }>; coverage_user_id?:string; + priority?: Array; + // Event Params reference?: string; location?: IEventLocation; @@ -1391,6 +1398,7 @@ export interface ISearchAPIParams { item_ids?: string; name?: string; tz_offset?: string; + time_zone?: string; full_text?: string; anpa_category?: string; subject?: string; @@ -1411,6 +1419,7 @@ export interface ISearchAPIParams { filter_id?: ISearchFilter['_id']; source?: string; coverage_user_id?:string; + priority?: string; // Event Params reference?: string; @@ -1704,6 +1713,7 @@ export interface IPlanningAppState { forms: IFormState; session: ISession; locks: ILockedItems; + vocabularies: {[id: string]: Array}; } export interface INominatimLocalityFields { @@ -2208,8 +2218,7 @@ export interface IPlanningAPI { getConfig(contentType: string): IProfileMultilingualDetails; }; getDefaultLanguage(profile: IPlanningContentProfile): IVocabularyItem['qcode']; - - + getDefaultValues(profile: IPlanningContentProfile): DeepPartial; patch(original: IPlanningContentProfile, updates: IPlanningContentProfile): Promise; showManagePlanningProfileModal(): Promise; showManageEventProfileModal(): Promise; diff --git a/client/planning-extension/package-lock.json b/client/planning-extension/package-lock.json index 32b87f439..7656bf67a 100644 --- a/client/planning-extension/package-lock.json +++ b/client/planning-extension/package-lock.json @@ -18,7 +18,7 @@ "acorn-jsx": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "integrity": "sha512-AU7pnZkguthwBjKgCg6998ByQNIMjbuDQZ8bb78QAFZwPfmKia8AIzgY/gWgqCjnht8JLdXmB4YxA0KaV60ncQ==", "dev": true, "requires": { "acorn": "^3.0.4" @@ -27,7 +27,7 @@ "acorn": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "integrity": "sha512-OLUyIIZ7mF5oaAUT1w0TFqQS81q3saT46x8t7ukpPjMNk+nbs4ZHhs7ToV8EWnLYLepjETXd4XaCE4uxkMeqUw==", "dev": true } } @@ -35,7 +35,7 @@ "ajv": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==", "dev": true, "requires": { "co": "^4.6.0", @@ -47,7 +47,7 @@ "ajv-keywords": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "integrity": "sha512-ZFztHzVRdGLAzJmpUT9LNFLe1YiVOEylcaNpEutM26PVTCtOD919IMfD01CgbRouB42Dd9atjx1HseC15DgOZA==", "dev": true }, "ansi-escapes": { @@ -59,13 +59,13 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", "dev": true }, "argparse": { @@ -77,35 +77,64 @@ "sprintf-js": "~1.0.2" } }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, "array-includes": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", - "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.5" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" } }, "array.prototype.flatmap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz", - "integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", "dev": true, "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1", - "function-bind": "^1.1.1" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.tosorted": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz", + "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" } }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", "dev": true, "requires": { "chalk": "^1.1.3", @@ -116,7 +145,7 @@ "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", "dev": true, "requires": { "ansi-styles": "^2.2.1", @@ -129,7 +158,7 @@ "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -154,9 +183,9 @@ } }, "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "call-bind": { @@ -172,7 +201,7 @@ "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "integrity": "sha512-UJiE1otjXPF5/x+T3zTnSFiTOEmJoGTD9HmBoxnCUwho61a2eSNn/VwtwuIBDAo2SEOv1AJ7ARI5gCmohFLu/g==", "dev": true, "requires": { "callsites": "^0.2.0" @@ -181,7 +210,7 @@ "callsites": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "integrity": "sha512-Zv4Dns9IbXXmPkgRRUjAaJQgfN4xX5p6+RQFhWUqscdvvK2xK/ZL8b3IXIJsj+4sD+f24NwnWy2BY8AJ82JB0A==", "dev": true }, "chalk": { @@ -218,7 +247,7 @@ "chardet": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "integrity": "sha512-j/Toj7f1z98Hh2cYo2BVr85EpIRWqUi7rtRSGxh/cqUjqrnJe9l9UE7IUGd2vQ2p+kSHLkSzObQPZPLUC6TQwg==", "dev": true }, "circular-json": { @@ -230,7 +259,7 @@ "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", "dev": true, "requires": { "restore-cursor": "^2.0.0" @@ -245,7 +274,7 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true }, "color-convert": { @@ -260,13 +289,13 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "concat-stream": { @@ -282,15 +311,15 @@ } }, "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", "dev": true, "requires": { "lru-cache": "^4.0.1", @@ -308,18 +337,19 @@ } }, "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", "dev": true, "requires": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "doctrine": { @@ -332,27 +362,65 @@ } }, "es-abstract": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", - "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", "dev": true, "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.10.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" } }, "es-to-primitive": { @@ -369,7 +437,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, "eslint": { @@ -427,27 +495,44 @@ "eslint-plugin-jasmine": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/eslint-plugin-jasmine/-/eslint-plugin-jasmine-2.10.1.tgz", - "integrity": "sha1-VzO3CedR9LxA4x4cFpib0s377Jc=", + "integrity": "sha512-dF2siVCguzZpEkqgRaJdR+dsBbXEQKog2tq7A0jYPHK+3qSD+E92f+Sb1jY5y4ua0j18FVIBzEm0yEBID/RdmQ==", "dev": true }, "eslint-plugin-react": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz", - "integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==", + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", + "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==", "dev": true, "requires": { - "array-includes": "^3.1.3", - "array.prototype.flatmap": "^1.2.4", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", "doctrine": "^2.1.0", - "has": "^1.0.3", + "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.0.4", - "object.entries": "^1.1.4", - "object.fromentries": "^2.0.4", - "object.values": "^1.1.4", - "prop-types": "^15.7.2", - "resolve": "^2.0.0-next.3", - "string.prototype.matchall": "^4.0.5" + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.8" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "eslint-scope": { @@ -483,18 +568,18 @@ "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -509,9 +594,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -542,7 +627,7 @@ "fast-deep-equal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "integrity": "sha512-fueX787WZKCV0Is4/T2cyAdM4+x1S3MXXOAhavE1ys/W42SHAPacLTQhucja22QBYrfGw50M2sRiXPtTGv9Ymw==", "dev": true }, "fast-json-stable-stringify": { @@ -554,13 +639,13 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" @@ -569,7 +654,7 @@ "file-entry-cache": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "integrity": "sha512-uXP/zGzxxFvFfcZGgBIwotm+Tdc55ddPAzF7iHshP4YGaXMww7rSF9peD9D1sui5ebONg5UobsZv+FfgEpGv/w==", "dev": true, "requires": { "flat-cache": "^1.2.1", @@ -588,10 +673,19 @@ "write": "^0.2.1" } }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "function-bind": { @@ -600,33 +694,61 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.3" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" } }, "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } @@ -637,10 +759,28 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "has": { @@ -655,30 +795,54 @@ "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", "dev": true, "requires": { "ansi-regex": "^2.0.0" } }, "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "dev": true }, "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -697,13 +861,13 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "requires": { "once": "^1.3.0", @@ -739,78 +903,99 @@ } }, "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", "dev": true, "requires": { - "get-intrinsic": "^1.1.0", + "get-intrinsic": "^1.2.0", "has": "^1.0.3", "side-channel": "^1.0.4" } }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, "is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", - "dev": true + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } }, "is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "requires": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true }, "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dev": true, "requires": { "has": "^1.0.3" } }, "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "dev": true + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true }, "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, "is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", - "dev": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" + "has-tostringtag": "^1.0.0" } }, "is-resolvable": { @@ -819,11 +1004,23 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", - "dev": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-symbol": { "version": "1.0.4", @@ -834,22 +1031,44 @@ "has-symbols": "^1.0.2" } }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", "dev": true }, "js-yaml": { @@ -865,29 +1084,29 @@ "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "integrity": "sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==", "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, "jsx-ast-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", - "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", + "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", "dev": true, "requires": { - "array-includes": "^3.1.2", - "object.assign": "^4.1.2" + "array-includes": "^3.1.5", + "object.assign": "^4.1.3" } }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, "requires": { "prelude-ls": "~1.1.2", @@ -926,27 +1145,27 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } }, "ms": { @@ -958,25 +1177,25 @@ "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", "dev": true }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true }, "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "dev": true }, "object-keys": { @@ -986,55 +1205,64 @@ "dev": true }, "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dev": true, "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" } }, "object.entries": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.4.tgz", - "integrity": "sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "object.fromentries": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.4.tgz", - "integrity": "sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has": "^1.0.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "object.hasown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", + "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "object.values": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", - "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "requires": { "wrappy": "1" @@ -1043,7 +1271,7 @@ "onetime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", "dev": true, "requires": { "mimic-fn": "^1.0.0" @@ -1066,19 +1294,19 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true }, "path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", "dev": true }, "path-parse": { @@ -1096,7 +1324,7 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true }, "process-nextick-args": { @@ -1112,20 +1340,20 @@ "dev": true }, "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" } }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", "dev": true }, "react-is": { @@ -1135,9 +1363,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -1150,13 +1378,14 @@ } }, "regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" } }, "regexpp": { @@ -1168,7 +1397,7 @@ "require-uncached": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "integrity": "sha512-Xct+41K3twrbBHdxAgMoOS+cNcoqIjfM2/VxBF4LL2hVph7YsF8VSKyQ3BDFZwEVbok9yeDl2le/qo0S77WG2w==", "dev": true, "requires": { "caller-path": "^0.1.0", @@ -1176,25 +1405,26 @@ } }, "resolve": { - "version": "2.0.0-next.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", - "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", "dev": true, "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-from": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "integrity": "sha512-kT10v4dhrlLNcnO084hEjvXCI1wUG9qZLoz2RogxqDQQYy7IxjI/iMUkOtQTNEh6rzHxvdQWHsJyel1pKOVCxg==", "dev": true }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", "dev": true, "requires": { "onetime": "^2.0.0", @@ -1219,13 +1449,13 @@ "rx-lite": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "integrity": "sha512-Cun9QucwK6MIrp3mry/Y7hqD1oFqTYLQ4pGxaHTjIdaFDWRGGLikqp6u8LcWJnzpoALg9hap+JGk8sFIUuEGNA==", "dev": true }, "rx-lite-aggregates": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "integrity": "sha512-3xPNZGW93oCjiO7PtKxRK6iOVYBWBvtf9QHDfU23Oc+dLIQmAV//UnyXV/yihv81VS/UqoQPk4NegS8EFi55Hg==", "dev": true, "requires": { "rx-lite": "*" @@ -1237,6 +1467,17 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1252,7 +1493,7 @@ "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -1261,7 +1502,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true }, "side-channel": { @@ -1276,9 +1517,9 @@ } }, "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, "slice-ansi": { @@ -1293,7 +1534,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "string-width": { @@ -1307,39 +1548,52 @@ } }, "string.prototype.matchall": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz", - "integrity": "sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", + "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.3.1", + "regexp.prototype.flags": "^1.4.3", "side-channel": "^1.0.4" } }, + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "string_decoder": { @@ -1354,16 +1608,16 @@ "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", "dev": true, "requires": { "ansi-regex": "^3.0.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true } } @@ -1371,7 +1625,7 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true }, "superdesk-code-style": { @@ -1389,7 +1643,13 @@ "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, "table": { @@ -1409,13 +1669,13 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, "tmp": { @@ -1430,16 +1690,27 @@ "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, "requires": { "prelude-ls": "~1.1.2" } }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "dev": true }, "typescript": { @@ -1449,21 +1720,21 @@ "dev": true }, "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, "which": { @@ -1488,6 +1759,20 @@ "is-symbol": "^1.0.3" } }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -1497,13 +1782,13 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, "write": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "integrity": "sha512-CJ17OoULEKXpA5pef3qLj5AxTJ6mSt7g84he2WIskKwqFO4T97d5V7Tadl0DYDk7qyUOQD5WlUlOMChaYrhxeA==", "dev": true, "requires": { "mkdirp": "^0.5.1" @@ -1512,7 +1797,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", "dev": true } } diff --git a/client/selectors/vocabs.ts b/client/selectors/vocabs.ts index 90c732faa..4606692bd 100644 --- a/client/selectors/vocabs.ts +++ b/client/selectors/vocabs.ts @@ -4,14 +4,16 @@ import {createSelector} from 'reselect'; import {IVocabularyItem} from 'superdesk-api'; import {IPlanningAppState} from '../interfaces'; -export const coverageProviders = (state) => get(state, 'vocabularies.coverage_providers', []); -export const locators = (state) => get(state, 'vocabularies.locators', []); -export const categories = (state) => get(state, 'vocabularies.categories', []); -export const subjects = (state) => get(state, 'subjects', []); +const EMPTY_ARRAY = []; + +export const coverageProviders = (state) => get(state, 'vocabularies.coverage_providers', EMPTY_ARRAY); +export const locators = (state) => get(state, 'vocabularies.locators', EMPTY_ARRAY); +export const categories = (state) => get(state, 'vocabularies.categories', EMPTY_ARRAY); +export const subjects = (state) => get(state, 'subjects', EMPTY_ARRAY); export const urgencyLabel = (state) => get(state, 'urgency.label', 'Urgency'); -export const eventOccurStatuses = (state) => get(state, 'vocabularies.eventoccurstatus', []); -export const getContactTypes = (state) => get(state, 'vocabularies.contact_type', []); -export const getLanguages = (state) => get(state, 'vocabularies.languages', []); +export const eventOccurStatuses = (state) => get(state, 'vocabularies.eventoccurstatus', EMPTY_ARRAY); +export const getContactTypes = (state) => get(state, 'vocabularies.contact_type', EMPTY_ARRAY); +export const getLanguages = (state) => get(state, 'vocabularies.languages', EMPTY_ARRAY); export const getLanguagesForTreeSelectInput = createSelector< IPlanningAppState, @@ -21,3 +23,18 @@ export const getLanguagesForTreeSelectInput = createSelector< [getLanguages], (languages) => (languages.map((language) => ({value: language}))) ); + +export const getPriorities = (state: IPlanningAppState) => state.vocabularies.priority ?? EMPTY_ARRAY; + +export const getPriorityQcodes = createSelector< + IPlanningAppState, + Array, + Array +>( + getPriorities, + (priorities) => ( + priorities + .map((item) => parseInt(item.qcode, 10)) + .sort() + ) +); diff --git a/client/utils/contentProfiles.ts b/client/utils/contentProfiles.ts index 4ca692704..9c1d3f2e9 100644 --- a/client/utils/contentProfiles.ts +++ b/client/utils/contentProfiles.ts @@ -235,6 +235,8 @@ export function getFieldNameTranslated(field: string): string { return gettext('Accreditation Info'); case 'accreditation_deadline': return gettext('Accreditation Deadline'); + case 'priority': + return gettext('Priority'); } return field; diff --git a/client/utils/events.ts b/client/utils/events.ts index 2d42eb4e9..0503a3182 100644 --- a/client/utils/events.ts +++ b/client/utils/events.ts @@ -534,70 +534,45 @@ function getDateStringForEvent( // !! Note - expects event dates as instance of moment() !! // const dateFormat = appConfig.planning.dateformat; const timeFormat = appConfig.planning.timeformat; - const start = get(event.dates, 'start'); - const end = get(event.dates, 'end'); + const start = getStartDate(event); + const end = getEndDate(event); const tz = get(event.dates, 'tz'); const localStart = timeUtils.getLocalDate(start, tz); + const isFullDay = event?.dates?.all_day; + const noEndTime = event?.dates?.no_end_time; + const multiDay = !start.isSame(end, 'day'); + let dateString, timezoneString = ''; - let timezoneForEvents = ''; - if (!start || !end) + if (!start || !end) { return; + } dateString = getTBCDateString(event, ' @ ', dateOnly); if (!dateString) { - if (start.isSame(end, 'day')) { - if (dateOnly) { + if (!multiDay) { + if (dateOnly || isFullDay) { dateString = start.format(dateFormat); + } else if (noEndTime) { + dateString = getDateTimeString(start, dateFormat, timeFormat, ' @ ', false); } else { dateString = getDateTimeString(start, dateFormat, timeFormat, ' @ ', false) + ' - ' + end.format(timeFormat); } - } else if (dateOnly) { + } else if (dateOnly || isFullDay) { dateString = start.format(dateFormat) + ' - ' + end.format(dateFormat); + } else if (noEndTime) { + dateString = getDateTimeString(start, dateFormat, timeFormat, ' @ ', false) + ' - ' + + end.format(dateFormat); } else { dateString = getDateTimeString(start, dateFormat, timeFormat, ' @ ', false) + ' - ' + - getDateTimeString(end, dateFormat, timeFormat, ' @ ', false); + getDateTimeString(end, dateFormat, timeFormat, ' @ ', false); } } - const isFullDay = event?.dates?.all_day; - const noEndTime = event?.dates?.no_end_time; - - const multiDay = !isEventSameDay(start, end); - - if (isFullDay && !multiDay) { - if (get(event.dates, 'all_day')) { - // use UTC mode to avoid any date conversion - return moment.utc(start).format(dateFormat); - } - - return start.format(dateFormat); - } else if (noEndTime && !multiDay) { - if (withTimezone) { - if (!useLocal) { - timezoneForEvents = - `(${getDateTimeString(start, dateFormat, timeFormat, ' @ ', true, tz ? tz : 'utc')})`; - } else { - timezoneForEvents = getDateTimeString(start, dateFormat, timeFormat, ' @ ', true); - } - } - return timezoneForEvents; - } else if (isFullDay && multiDay) { - return timezoneForEvents = start.format(dateFormat) + ' - ' + end.format(dateFormat); - } else if (noEndTime && multiDay) { - if (withTimezone) { - if (!useLocal && tz) { - timezoneForEvents = - `(${getDateTimeString(start, dateFormat, timeFormat, ' @ ', true, tz) + ' - ' + - end.format(dateFormat)})`; - } else { - timezoneForEvents = - getDateTimeString(start, dateFormat, timeFormat, ' @ ', true) + ' - ' + - moment.utc(end).format(dateFormat); - } - } - return timezoneForEvents; + // no timezone info needed + if (isFullDay || dateOnly) { + return multiDay ? start.format(dateFormat) + ' - ' + end.format(dateFormat) : start.format(dateFormat); } if (withTimezone) { @@ -882,34 +857,82 @@ function getFlattenedEventsByDate(events: Array, startDate: moment.M return flatten(sortBy(eventsList, [(e) => (e.date)]).map((e) => e.events.map((k) => [e.date, k._id]))); } + +const getStartDate = (event: IEventItem) => ( + event.dates?.all_day ? moment.utc(event.dates.start) : moment(event.dates?.start) +); + +const getEndDate = (event: IEventItem) => ( + (event.dates?.all_day || event.dates?.no_end_time) ? moment.utc(event.dates.end) : moment(event.dates?.end) +); + +const isEventInRange = ( + event: IEventItem, + eventStart: moment.Moment, + eventEnd: moment.Moment, + start: moment.Moment, + end?: moment.Moment, +) => { + let localStart = eventStart; + let localEnd = eventEnd; + let startUnit : moment.unitOfTime.StartOf = 'second'; + let endUnit : moment.unitOfTime.StartOf = 'second'; + + if (event.dates?.all_day) { + // we have only dates in utc + localStart = moment(eventStart.format('YYYY-MM-DD')); + localEnd = moment(eventEnd.format('YYYY-MM-DD')); + startUnit = 'day'; + endUnit = 'day'; + } + + if (event.dates?.no_end_time) { + // we have time for start, but only date for end + localStart = moment(eventStart); + localEnd = moment(eventEnd.format('YYYY-MM-DD')); + endUnit = 'day'; + } + + return localEnd.isSameOrAfter(start, endUnit) && (end == null || localStart.isSameOrBefore(end, startUnit)); +}; + /* * Groups the events by date */ -function getEventsByDate(events: Array, startDate: moment.Moment, endDate: moment.Moment) { - if (!get(events, 'length', 0)) return []; +function getEventsByDate( + events: Array, + startDate: moment.Moment, + endDate: moment.Moment +): Array { + if ((events?.length ?? 0) === 0) { + return []; + } + // check if search exists // order by date - let sortedEvents = events.sort((a, b) => a.dates.start - b.dates.start); + let sortedEvents = events.sort((a, b) => { + const startA = getStartDate(a); + const startB = getStartDate(b); - const days = {}; + return startA.diff(startB); + }); + + const days: {[date: string]: Array} = {}; function addEventToDate(event: IEventItem, date?: moment.Moment) { - let eventDate = date || event.dates.start; - let eventStart = event.dates.start; - let eventEnd = event.dates.end; + let eventDate = date || getStartDate(event); + let eventStart = getStartDate(event); + let eventEnd = getEndDate(event); - if (!event.dates.start.isSame(event.dates.end, 'day')) { + if (!eventStart.isSame(eventEnd, 'day') && !event.dates.all_day && !event.dates.no_end_time) { eventStart = eventDate; - eventEnd = event.dates.end.isSame(eventDate, 'day') ? - event.dates.end : moment(eventDate.format('YYYY-MM-DD'), 'YYYY-MM-DD').add(86399, 'seconds'); + eventEnd = eventEnd.isSame(eventDate, 'day') ? + eventEnd : + moment(eventDate.format('YYYY-MM-DD'), 'YYYY-MM-DD').add(86399, 'seconds'); } - if (!(isDateInRange(startDate, eventStart, eventEnd) || - isDateInRange(endDate, eventStart, eventEnd))) { - if (!isDateInRange(eventStart, startDate, endDate) && - !isDateInRange(eventEnd, startDate, endDate)) { - return; - } + if (!isEventInRange(event, eventDate, eventEnd, startDate, endDate)) { + return; } let eventDateFormatted = eventDate.format('YYYY-MM-DD'); @@ -920,27 +943,27 @@ function getEventsByDate(events: Array, startDate: moment.Moment, en let evt = cloneDeep(event); - evt._sortDate = eventDate; - + evt._sortDate = eventStart; days[eventDateFormatted].push(evt); } sortedEvents.forEach((event) => { // compute the number of days of the event - let ending = event.actioned_date ? event.actioned_date : event.dates.end; + const eventEndDate = event.actioned_date ? moment(event.actioned_date) : getEndDate(event); + const eventStartDate = getStartDate(event); - if (!event.dates.start.isSame(ending, 'day')) { - let deltaDays = Math.max(Math.ceil(ending.diff(event.dates.start, 'days', true)), 1); - // if the event happens during more that one day, add it to every day + if (!eventStartDate.isSame(eventEndDate, 'day')) { + let deltaDays = Math.max(Math.ceil(eventEndDate.diff(eventStartDate, 'days', true)), 1); + // if the event happens during more than one day, add it to every day // add the event to the other days - for (let i = 1; i <= deltaDays; i++) { - // clone the date - const newDate = moment(event.dates.start.format('YYYY-MM-DD'), 'YYYY-MM-DD', true); + for (let i = 1; i < deltaDays; i++) { + // clone the date + const newDate = moment(eventStartDate.format('YYYY-MM-DD'), 'YYYY-MM-DD', true); newDate.add(i, 'days'); - if (newDate.isSameOrBefore(ending, 'day')) { + if (newDate.isSameOrBefore(eventEndDate, 'day')) { addEventToDate(event, newDate); } } @@ -948,7 +971,7 @@ function getEventsByDate(events: Array, startDate: moment.Moment, en // add event to its initial starting date // add an event only if it's not actioned or actioned after this event's start date - if (!event.actioned_date || event.actioned_date.isSameOrAfter(event.dates.start, 'date')) { + if (!event.actioned_date || moment(event.actioned_date).isSameOrAfter(eventStartDate, 'date')) { addEventToDate(event); } }); @@ -1107,28 +1130,34 @@ function defaultEventValues( defaultCalendars: IEventItem['calendars'], defaultPlaceList: IEventItem['place'] ): Partial { + const {contentProfiles} = planningApi; + const eventProfile = contentProfiles.get('event'); + const defaultValues = contentProfiles.getDefaultValues(eventProfile) as Partial; const occurStatus = getItemInArrayById(occurStatuses, 'eocstat:eos5', 'qcode') || { label: 'Confirmed', qcode: 'eocstat:eos5', name: 'Planned, occurs certainly', }; - const language = planningApi.contentProfiles.getDefaultLanguage(planningApi.contentProfiles.get('event')); - - let newEvent: Partial = { - type: 'event', - occur_status: occurStatus, - dates: { - start: null, - end: null, - tz: timeUtils.localTimeZone(), + const language = contentProfiles.getDefaultLanguage(eventProfile); + + let newEvent: Partial = Object.assign( + { + type: 'event', + occur_status: occurStatus, + dates: { + start: null, + end: null, + tz: timeUtils.localTimeZone(), + }, + calendars: defaultCalendars, + state: 'draft', + _startTime: null, + _endTime: null, + language: language, + languages: [language], }, - calendars: defaultCalendars, - state: 'draft', - _startTime: null, - _endTime: null, - language: language, - languages: [language], - }; + defaultValues + ); if (defaultPlaceList) { newEvent.place = defaultPlaceList; @@ -1345,6 +1374,8 @@ const self = { getFlattenedEventsByDate, isEventCompleted, fillEventTime, + getStartDate, + getEndDate, }; export default self; diff --git a/client/utils/index.ts b/client/utils/index.ts index b42178d3e..6782b7bf3 100644 --- a/client/utils/index.ts +++ b/client/utils/index.ts @@ -946,6 +946,7 @@ export const sortBasedOnTBC = (days) => { } pushEventsForTheDay(days); + return sortBy(sortable, [(e) => (e.date)]); }; diff --git a/client/utils/planning.ts b/client/utils/planning.ts index 24aa91fa5..2897bd369 100644 --- a/client/utils/planning.ts +++ b/client/utils/planning.ts @@ -813,8 +813,9 @@ function createNewPlanningFromNewsItem( user, contentTypes ); - + const {contentProfiles} = planningApi; let newPlanning: Partial = { + ...contentProfiles.getDefaultValues(contentProfiles.get('planning')), type: 'planning', slugline: addNewsItemToPlanning.slugline, headline: get(addNewsItemToPlanning, 'headline'), @@ -828,6 +829,10 @@ function createNewPlanningFromNewsItem( language: addNewsItemToPlanning.language, }; + if (addNewsItemToPlanning.priority != null) { + newPlanning.priority = addNewsItemToPlanning.priority; + } + if (get(addNewsItemToPlanning, 'flags.marked_for_not_publication')) { newPlanning.flags = {marked_for_not_publication: true}; } @@ -856,6 +861,7 @@ function createCoverageFromNewsItem( ); newCoverage.planning = { + ...newCoverage.planning, g2_content_type: get(contentType, 'qcode', PLANNING.G2_CONTENT_TYPE.TEXT), slugline: get(addNewsItemToPlanning, 'slugline', ''), ednote: get(addNewsItemToPlanning, 'ednote', ''), @@ -863,6 +869,10 @@ function createCoverageFromNewsItem( .startOf('hour'), }; + if (addNewsItemToPlanning.priority != null) { + newCoverage.planning.priority = addNewsItemToPlanning.priority; + } + if (addNewsItemToPlanning.language != null) { newCoverage.planning.language = addNewsItemToPlanning.language; } @@ -1264,18 +1274,24 @@ function shouldLockPlanningForEdit(item: IPlanningItem, privileges: IPrivileges) ); } -function defaultPlanningValues(currentAgenda: IAgenda, defaultPlaceList: Array): Partial { - const language = planningApi.contentProfiles.getDefaultLanguage(planningApi.contentProfiles.get('planning')); - const newPlanning: Partial = { - type: 'planning', - planning_date: moment(), - agendas: get(currentAgenda, 'is_enabled') ? - [getItemId(currentAgenda)] : [], - state: 'draft', - item_class: 'plinat:newscoverage', - language: language, - languages: [language], - }; +function defaultPlanningValues(currentAgenda?: IAgenda, defaultPlaceList?: Array): Partial { + const {contentProfiles} = planningApi; + const planningProfile = contentProfiles.get('planning'); + const defaultValues = contentProfiles.getDefaultValues(planningProfile) as Partial; + const language = contentProfiles.getDefaultLanguage(planningProfile); + const newPlanning: Partial = Object.assign( + { + type: 'planning', + planning_date: moment(), + agendas: get(currentAgenda, 'is_enabled') ? + [getItemId(currentAgenda)] : [], + state: 'draft', + item_class: 'plinat:newscoverage', + language: language, + languages: [language], + }, + defaultValues + ); if (defaultPlaceList) { newPlanning.place = defaultPlaceList; @@ -1296,38 +1312,48 @@ function defaultCoverageValues( defaultDesk?: IDesk, preferredCoverageDesks?: {[key: string]: IDesk['_id']}, ): DeepPartial { + const {contentProfiles} = planningApi; + const coverageProfile = contentProfiles.get('coverage'); + const defaultValues = (contentProfiles.getDefaultValues(coverageProfile)) as DeepPartial; let newCoverage: DeepPartial = { coverage_id: generateTempId(), - planning: { - slugline: stringUtils.convertStringFieldForProfileFieldType( - 'planning', - 'coverage', - 'slugline', - 'slugline', - planningItem?.slugline - ), - internal_note: stringUtils.convertStringFieldForProfileFieldType( - 'planning', - 'coverage', - 'internal_note', - 'internal_note', - planningItem?.internal_note - ), - ednote: stringUtils.convertStringFieldForProfileFieldType( - 'planning', - 'coverage', - 'ednote', - 'ednote', - planningItem?.ednote - ), - scheduled: planningItem?.planning_date || moment(), - g2_content_type: g2contentType, - language: planningItem?.language ?? eventItem?.language, - }, + planning: Object.assign( + { + slugline: stringUtils.convertStringFieldForProfileFieldType( + 'planning', + 'coverage', + 'slugline', + 'slugline', + planningItem?.slugline + ), + internal_note: stringUtils.convertStringFieldForProfileFieldType( + 'planning', + 'coverage', + 'internal_note', + 'internal_note', + planningItem?.internal_note + ), + ednote: stringUtils.convertStringFieldForProfileFieldType( + 'planning', + 'coverage', + 'ednote', + 'ednote', + planningItem?.ednote + ), + scheduled: planningItem?.planning_date || moment(), + g2_content_type: g2contentType, + language: planningItem?.language ?? eventItem?.language, + }, + defaultValues + ), news_coverage_status: getDefaultCoverageStatus(newsCoverageStatus), workflow_status: 'draft', }; + if (planningItem?.priority && newCoverage.planning.priority == null) { + newCoverage.planning.priority = planningItem.priority; + } + if (planningItem?._time_to_be_confirmed) { newCoverage._time_to_be_confirmed = planningItem._time_to_be_confirmed; } diff --git a/client/utils/search.ts b/client/utils/search.ts index 9fd885841..753a81492 100644 --- a/client/utils/search.ts +++ b/client/utils/search.ts @@ -13,7 +13,7 @@ import { SORT_ORDER, } from '../interfaces'; import {MAIN} from '../constants'; -import {getTimeZoneOffset} from './index'; +import {getTimeZoneOffset, timeUtils} from './index'; function commonParamsToSearchParams(params: ICommonSearchParams): ISearchParams { return { @@ -29,6 +29,7 @@ function commonParamsToSearchParams(params: ICommonSearchParams { ednote: 'edit my note', scheduled: moment().add(1, 'hour') .startOf('hour'), + internal_note: undefined, + language: undefined, }, news_coverage_status: {qcode: 'ncostat:int'}, workflow_status: 'active', @@ -344,6 +346,8 @@ describe('PlanningUtils', () => { ednote: 'edit my note', scheduled: moment(newsItem.firstpublished).add(1, 'hour') .startOf('hour'), + internal_note: undefined, + language: undefined, }, news_coverage_status: {qcode: 'ncostat:int'}, workflow_status: 'active', @@ -380,6 +384,8 @@ describe('PlanningUtils', () => { ednote: 'edit my note', scheduled: moment(newsItem.firstpublished).add(1, 'hour') .startOf('hour'), + internal_note: undefined, + language: undefined, }, news_coverage_status: {qcode: 'ncostat:int'}, workflow_status: 'active', @@ -417,6 +423,8 @@ describe('PlanningUtils', () => { ednote: 'edit my note', scheduled: moment(newsItem.schedule_settings.utc_publish_schedule).add(1, 'hour') .startOf('hour'), + internal_note: undefined, + language: undefined, }, news_coverage_status: {qcode: 'ncostat:int'}, workflow_status: 'active', @@ -478,9 +486,9 @@ describe('PlanningUtils', () => { urgency: 3, description_text: 'some abstractions', place: [{name: 'Australia'}], - coverages: [{ + coverages: [jasmine.objectContaining({ coverage_id: jasmine.any(String), - planning: { + planning: jasmine.objectContaining({ g2_content_type: 'text', slugline: 'slugger', ednote: 'Edit my note!', @@ -488,7 +496,7 @@ describe('PlanningUtils', () => { .startOf('hour'), _scheduledTime: moment().add(1, 'hour') .startOf('hour'), - }, + }), news_coverage_status: {qcode: 'ncostat:int'}, workflow_status: 'active', assigned_to: { @@ -496,7 +504,7 @@ describe('PlanningUtils', () => { user: 'ident1', priority: ASSIGNMENTS.DEFAULT_PRIORITY, }, - }], + })], })); }); @@ -533,9 +541,9 @@ describe('PlanningUtils', () => { urgency: 3, description_text: 'some abstractions', flags: {marked_for_not_publication: true}, - coverages: [{ + coverages: [jasmine.objectContaining({ coverage_id: jasmine.any(String), - planning: { + planning: jasmine.objectContaining({ g2_content_type: 'text', slugline: 'slugger', ednote: 'Edit my note!', @@ -543,7 +551,7 @@ describe('PlanningUtils', () => { .startOf('hour'), _scheduledTime: moment().add(1, 'hour') .startOf('hour'), - }, + }), news_coverage_status: {qcode: 'ncostat:int'}, workflow_status: 'active', assigned_to: { @@ -551,7 +559,7 @@ describe('PlanningUtils', () => { user: 'ident1', priority: ASSIGNMENTS.DEFAULT_PRIORITY, }, - }], + })], })); }); }); diff --git a/client/validators/index.ts b/client/validators/index.ts index d9eb01fa8..93e77b3ac 100644 --- a/client/validators/index.ts +++ b/client/validators/index.ts @@ -77,9 +77,22 @@ export const validateItem = ({ switch (true) { case schema.required: - if (isEmpty(diff[key]) && isEmpty(getSubject(diff, key)) - && fieldsToValidate == null - || (Array.isArray(fieldsToValidate) && fieldsToValidate.includes(key))) { + if ( + (( + schema.type !== 'integer' && + isEmpty(diff[key]) + ) || + ( + schema.type === 'integer' && + diff[key] == null + )) && + isEmpty(getSubject(diff, key)) && + fieldsToValidate == null || + ( + Array.isArray(fieldsToValidate) && + fieldsToValidate.includes(key) + ) + ) { errors[key] = gettext('This field is required'); messages.push(gettext('{{ key }} is a required field', {key: key.toUpperCase()})); } else if (errors[key]) { diff --git a/e2e/cypress/e2e/events/edit_event.cy.ts b/e2e/cypress/e2e/events/edit_event.cy.ts index cf3072ed9..0c78b9631 100644 --- a/e2e/cypress/e2e/events/edit_event.cy.ts +++ b/e2e/cypress/e2e/events/edit_event.cy.ts @@ -1,10 +1,14 @@ -import {setup, login, waitForPageLoad, SubNavBar, Workqueue, Modal} from '../../support/common'; +import {cloneDeep} from 'lodash'; + +import {setup, login, waitForPageLoad, SubNavBar, Workqueue, Modal, addItems} from '../../support/common'; import {EventEditor, PlanningList} from '../../support/planning'; +import {TEST_EVENTS} from '../../fixtures/events'; + +const list = new PlanningList(); +const editor = new EventEditor(); describe('Planning.Events: edit metadata', () => { - const editor = new EventEditor(); const subnav = new SubNavBar(); - const list = new PlanningList(); const workqueue = new Workqueue(); const modal = new Modal(); let event; @@ -173,3 +177,60 @@ describe('Planning.Events: edit metadata', () => { .should('be.enabled'); }); }); + +describe('Planing.Events: edit existing events', () => { + beforeEach(() => { + setup({fixture_profile: 'planning_prepopulate_data'}, '/#/planning'); + addItems('events', [{ + ...cloneDeep(TEST_EVENTS.date_01_02_2045), + dates: { + start: TEST_EVENTS.date_01_02_2045.dates.start, + end: TEST_EVENTS.date_01_02_2045.dates.end, + }, + }, { + ...cloneDeep(TEST_EVENTS.date_02_02_2045), + dates: { + start: TEST_EVENTS.date_02_02_2045.dates.start, + end: TEST_EVENTS.date_02_02_2045.dates.end, + tz: null, + }, + }]); + login(); + + waitForPageLoad.planning(); + }); + + it('SDESK-6972: Edit events with no timezone', () => { + // Test if we can edit an Event without a timezone value + list.item(0) + .dblclick(); + editor.waitTillOpen(); + editor.waitLoadingComplete(); + + editor.type({definition_short: 'Modifying 1st event'}); + editor.waitForAutosave(); + editor.saveButton + .should('exist') + .click(); + editor.closeButton + .should('exist') + .click(); + editor.waitTillClosed(); + + // test if we can edit an Event with a timezone value of `null` + list.item(1) + .dblclick(); + editor.waitTillOpen(); + editor.waitLoadingComplete(); + + editor.type({definition_short: 'Modifying 2nd event'}); + editor.waitForAutosave(); + editor.saveButton + .should('exist') + .click(); + editor.closeButton + .should('exist') + .click(); + editor.waitTillClosed(); + }); +}); diff --git a/e2e/cypress/e2e/events/event_action_create_planning.cy.ts b/e2e/cypress/e2e/events/event_action_create_planning.cy.ts index 41a35cbb7..312eaebdd 100644 --- a/e2e/cypress/e2e/events/event_action_create_planning.cy.ts +++ b/e2e/cypress/e2e/events/event_action_create_planning.cy.ts @@ -1,7 +1,7 @@ import moment from 'moment-timezone'; import {setup, login, addItems, waitForPageLoad} from '../../support/common'; -import {TIME_STRINGS} from '../../support/utils/time'; +import {TIMEZONE} from '../../support/utils/time'; import {PlanningList, PlanningPreview, EventEditor, PlanningEditor} from '../../support/planning'; describe('Planning.Events: create planning action', () => { @@ -16,10 +16,8 @@ describe('Planning.Events: create planning action', () => { const expectedValues = { slugline: 'Original', - 'planning_date.date': '12/12/2045', - 'planning_date.time': moment('2045-12-11' + TIME_STRINGS[0]) - .tz('Australia/Sydney') - .format('HH:00'), + 'planning_date.date': '12/12/2025', + 'planning_date.time': '01:00', description_text: 'Desc.', ednote: 'Ed. Note', anpa_category: ['Finance'], @@ -28,6 +26,7 @@ describe('Planning.Events: create planning action', () => { beforeEach(() => { setup({fixture_profile: 'planning_prepopulate_data'}, '/#/planning'); + const start = moment.tz("2025-12-12 01:00", TIMEZONE).utc(); addItems('events', [{ slugline: 'Original', definition_short: 'Desc.', @@ -37,9 +36,9 @@ describe('Planning.Events: create planning action', () => { qcode: 'eocstat:eos5', }, dates: { - start: '2045-12-11' + TIME_STRINGS[0], - end: '2045-12-11' + TIME_STRINGS[1], - tz: 'Australia/Sydney', + start: start.format("YYYY-MM-DDTHH:mm:ss+0000"), + end: start.add(1, 'h').format('YYYY-MM-DDTHH:mm:ss+0000'), + tz: TIMEZONE, }, anpa_category: [{is_active: true, name: 'Finance', qcode: 'f', subject: '04000000'}], subject: [{parent: '15000000', name: 'sports awards', qcode: '15103000'}], diff --git a/e2e/cypress/e2e/search/search_combined.cy.ts b/e2e/cypress/e2e/search/search_combined.cy.ts index 1252703db..64aa06fbe 100644 --- a/e2e/cypress/e2e/search/search_combined.cy.ts +++ b/e2e/cypress/e2e/search/search_combined.cy.ts @@ -122,8 +122,8 @@ describe('Search.Combined: searching events and planning', () => { ], }, { params: { - 'start_date.date': '12/12/2025', - 'end_date.date': '12/12/2025', + 'start_date.date': '12/12/2045', + 'end_date.date': '12/12/2045', }, expectedCount: 0, clearAfter: true, diff --git a/e2e/cypress/e2e/search/search_events.cy.ts b/e2e/cypress/e2e/search/search_events.cy.ts index 7eaf5d37f..ae1c0cb35 100644 --- a/e2e/cypress/e2e/search/search_events.cy.ts +++ b/e2e/cypress/e2e/search/search_events.cy.ts @@ -156,8 +156,8 @@ describe('Search.Events: searching events', () => { ] }, { params: { - 'start_date.date': '12/12/2025', - 'end_date.date': '12/12/2025', + 'start_date.date': '12/12/2045', + 'end_date.date': '12/12/2045', }, expectedCount: 0, clearAfter: true, diff --git a/e2e/cypress/e2e/search/search_filters.cy.ts b/e2e/cypress/e2e/search/search_filters.cy.ts index ce8275993..55038d6dc 100644 --- a/e2e/cypress/e2e/search/search_filters.cy.ts +++ b/e2e/cypress/e2e/search/search_filters.cy.ts @@ -27,8 +27,8 @@ describe('Search.Filters: creating search filters', () => { state: ['Postponed'], only_posted: true, lock_state: 'Locked', - 'start_date.date': '12/12/2025', - 'end_date.date': '12/12/2025', + 'start_date.date': '12/12/2045', + 'end_date.date': '12/12/2045', calendars: ['Finance'], agendas: ['Sports'], }); @@ -78,8 +78,8 @@ describe('Search.Filters: creating search filters', () => { state: ['Postponed'], only_posted: true, lock_state: 'Locked', - 'start_date.date': '12/12/2025', - 'end_date.date': '12/12/2025', + 'start_date.date': '12/12/2045', + 'end_date.date': '12/12/2045', calendars: ['Sport'], source: ['aap'], location: 'Sydney Opera House', @@ -131,8 +131,8 @@ describe('Search.Filters: creating search filters', () => { state: ['Postponed'], only_posted: true, lock_state: 'Locked', - 'start_date.date': '12/12/2025', - 'end_date.date': '12/12/2025', + 'start_date.date': '12/12/2045', + 'end_date.date': '12/12/2045', agendas: ['Sports'], urgency: '3', g2_content_type: 'Video', diff --git a/e2e/cypress/e2e/search/search_planning.cy.ts b/e2e/cypress/e2e/search/search_planning.cy.ts index 70034b15c..e41e54445 100644 --- a/e2e/cypress/e2e/search/search_planning.cy.ts +++ b/e2e/cypress/e2e/search/search_planning.cy.ts @@ -127,8 +127,8 @@ describe('Search.Planning: searching planning items', () => { ], }, { params: { - 'start_date.date': '12/12/2025', - 'end_date.date': '12/12/2025', + 'start_date.date': '12/12/2045', + 'end_date.date': '12/12/2045', }, expectedCount: 0, clearAfter: true, diff --git a/e2e/cypress/fixtures/events.ts b/e2e/cypress/fixtures/events.ts index 07658bfa7..f68b297ef 100644 --- a/e2e/cypress/fixtures/events.ts +++ b/e2e/cypress/fixtures/events.ts @@ -1,4 +1,4 @@ -import {getDateStringFor, TIME_STRINGS} from '../support/utils/time'; +import {getDateStringFor, TIME_STRINGS, TIMEZONE} from '../support/utils/time'; export const LOCATIONS = { sydney_opera_house: { @@ -47,7 +47,7 @@ export const TEST_EVENTS = { dates: { start: '2045-12-11' + TIME_STRINGS[0], end: '2045-12-11' + TIME_STRINGS[1], - tz: 'Australia/Sydney', + tz: TIMEZONE, }, name: 'Test', slugline: 'Original', @@ -73,7 +73,7 @@ export const TEST_EVENTS = { dates: { start: '2045-12-11' + TIME_STRINGS[0], end: '2045-12-11' + TIME_STRINGS[1], - tz: 'Australia/Sydney', + tz: TIMEZONE, }, state: 'spiked', name: 'Spiker', @@ -82,9 +82,9 @@ export const TEST_EVENTS = { date_01_02_2045: { ...BASE_EVENT, dates: { - start: '2045-01-31' + TIME_STRINGS[0], - end: '2045-01-31' + TIME_STRINGS[1], - tz: 'Australia/Sydney', + start: '2045-02-01T00:00:00+0000', + end: '2045-02-01T01:00:00+0000', + tz: 'UTC', }, name: 'February 1st 2045', slugline: 'Event Feb 1', @@ -92,9 +92,9 @@ export const TEST_EVENTS = { date_02_02_2045: { ...BASE_EVENT, dates: { - start: '2045-02-01' + TIME_STRINGS[0], - end: '2045-02-01' + TIME_STRINGS[1], - tz: 'Australia/Sydney', + start: '2045-02-02T00:00:00+0000', + end: '2045-02-02T01:00:00+0000', + tz: 'UTC', }, name: 'February 2nd 2045', slugline: 'Event Feb 2', @@ -102,9 +102,9 @@ export const TEST_EVENTS = { date_03_02_2045: { ...BASE_EVENT, dates: { - start: '2045-02-02' + TIME_STRINGS[0], - end: '2045-02-02' + TIME_STRINGS[1], - tz: 'Australia/Sydney', + start: '2045-02-03T00:00:00+0000', + end: '2045-02-03T01:00:00+0000', + tz: 'UTC', }, name: 'February 3rd 2045', slugline: 'Event Feb 3', @@ -112,9 +112,9 @@ export const TEST_EVENTS = { date_04_02_2045: { ...BASE_EVENT, dates: { - start: '2045-02-03' + TIME_STRINGS[0], - end: '2045-02-03' + TIME_STRINGS[1], - tz: 'Australia/Sydney', + start: '2045-02-04T00:00:00+0000', + end: '2045-02-04T01:00:00+0000', + tz: 'UTC', }, name: 'February 4th 2045', slugline: 'Event Feb 4', @@ -127,7 +127,7 @@ function getEventForDate(dateString: string, metadata: {[key: string]: any} = {} dates: { start: dateString + TIME_STRINGS[0], end: dateString + TIME_STRINGS[1], - tz: 'Australia/Sydney', + tz: TIMEZONE, }, ...metadata, }; diff --git a/e2e/cypress/fixtures/planning.ts b/e2e/cypress/fixtures/planning.ts index 573e61971..707b257fc 100644 --- a/e2e/cypress/fixtures/planning.ts +++ b/e2e/cypress/fixtures/planning.ts @@ -15,7 +15,7 @@ export const TEST_PLANNINGS = { draft: { ...BASE_PLANNING, slugline: 'Original', - planning_date: '2045-12-11' + TIME_STRINGS[1], + planning_date: '2045-12-11T01:00:00+0000', anpa_category: [ {name: 'Overseas Sport', qcode: 's'}, {name: 'International News', qcode: 'i'}, @@ -28,34 +28,34 @@ export const TEST_PLANNINGS = { spiked: { ...BASE_PLANNING, slugline: 'Spiker', - planning_date: '2045-12-11' + TIME_STRINGS[1], + planning_date: '2045-12-11T01:00:00+0000', state: 'spiked', }, featured: { ...BASE_PLANNING, slugline: 'Featured Planning', - planning_date: '2045-12-12' + TIME_STRINGS[1], + planning_date: '2045-12-12T01:00:00+0000', featured: true, }, plan_date_01_02_2045: { ...BASE_PLANNING, slugline: 'Plan Feb 1', - planning_date: '2045-01-31' + TIME_STRINGS[1], + planning_date: '2045-02-01T01:00:00+0000', }, plan_date_02_02_2045: { ...BASE_PLANNING, slugline: 'Plan Feb 2', - planning_date: '2045-02-01' + TIME_STRINGS[1], + planning_date: '2045-02-02T01:00:00+0000', }, plan_date_03_02_2045: { ...BASE_PLANNING, slugline: 'Plan Feb 3', - planning_date: '2045-02-02' + TIME_STRINGS[1], + planning_date: '2045-02-03T01:00:00+0000', }, plan_date_04_02_2045: { ...BASE_PLANNING, slugline: 'Plan Feb 4', - planning_date: '2045-02-03' + TIME_STRINGS[1], + planning_date: '2045-02-04T01:00:00+0000', }, }; diff --git a/e2e/cypress/support/common/inputs/treeSelect.ts b/e2e/cypress/support/common/inputs/treeSelect.ts index 25ece35a6..df7f2647e 100644 --- a/e2e/cypress/support/common/inputs/treeSelect.ts +++ b/e2e/cypress/support/common/inputs/treeSelect.ts @@ -21,8 +21,7 @@ export class TreeSelect extends Input { cy.wrap(values).each((value: string) => { this.addButton.click(); cy.get('body').type(value); - this.element - .find('.suggestion-item--bgcolor') + cy.get('[data-test-id="tree-select-popover"] ul li') .eq(0) .should('exist') .click(); @@ -32,8 +31,7 @@ export class TreeSelect extends Input { this.addButton.click(); cy.get('body').type(value); - this.element - .find('.suggestion-item--bgcolor') + cy.get('[data-test-id="tree-select-popover"] ul li') .eq(0) .should('exist') .click(); diff --git a/e2e/cypress/support/utils/time.ts b/e2e/cypress/support/utils/time.ts index 7d2ba5aef..bd37fc049 100644 --- a/e2e/cypress/support/utils/time.ts +++ b/e2e/cypress/support/utils/time.ts @@ -1,8 +1,10 @@ import moment from 'moment-timezone'; +export const TIMEZONE = moment.tz.guess(); + export function getStartOfNextWeek(): moment.Moment { const startOfWeek = 0; - let current = (moment.tz('Australia/Sydney')).set({ + let current = (moment()).set({ hour: 0, minute: 0, second: 0, @@ -26,19 +28,16 @@ export function getStartOfNextWeek(): moment.Moment { } export const getDateStringFor = { - today: () => moment - .tz('Australia/Sydney') + today: () => moment() .set({hour: 0}) .utc() .format('YYYY-MM-DD'), - yesterday: () => moment - .tz('Australia/Sydney') + yesterday: () => moment() .set({hour: 0}) .utc() .subtract(1, 'd') .format('YYYY-MM-DD'), - tomorrow: () => moment - .tz('Australia/Sydney') + tomorrow: () => moment() .set({hour: 0}) .utc() .add(1, 'd') @@ -49,7 +48,6 @@ export const getDateStringFor = { export function getTimeStringForHour(hour: number): string { return moment() - .tz('Australia/Sydney') .set({hour: hour}) .utc() .format('THH:00:00+0000'); diff --git a/e2e/package.json b/e2e/package.json index f01421f22..952cea03e 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -12,8 +12,8 @@ "cypress-terminal-report": "^5.0.2", "extract-text-webpack-plugin": "3.0.2", "lodash": "^4.17.15", - "moment": "2.20.1", - "moment-timezone": "0.5.14" + "moment": "^2.29.4", + "moment-timezone": "^0.5.42" }, "scripts": { "cypress-ui": "cypress open", diff --git a/e2e/server/gunicorn_config.py b/e2e/server/gunicorn_config.py index 403e8e599..c0db34090 100644 --- a/e2e/server/gunicorn_config.py +++ b/e2e/server/gunicorn_config.py @@ -1,8 +1,7 @@ import os -import multiprocessing bind = '0.0.0.0:5000' -workers = int(os.environ.get('WEB_CONCURRENCY', multiprocessing.cpu_count() * 2 + 1)) +workers = 3 loglevel = 'warning' diff --git a/package-lock.json b/package-lock.json index a6b5d6363..ebaabb688 100644 --- a/package-lock.json +++ b/package-lock.json @@ -254,9 +254,9 @@ } }, "@types/jasmine": { - "version": "3.10.6", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.10.6.tgz", - "integrity": "sha512-twY9adK/vz72oWxCWxzXaxoDtF9TpfEEsxvbc1ibjF3gMD/RThSuSud/GKUTR3aJnfbivAbC/vLqhY+gdWCHfA==", + "version": "3.10.15", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.10.15.tgz", + "integrity": "sha512-NdWern4OhbU7QcdlpPnvqy7LqpEjiAQ47tHDRdUKyGcwnhdmTsGniSJCC2B9ODiYiRnP53v6HOzu8B5/bqOtUw==", "dev": true }, "@types/lodash": { @@ -458,9 +458,9 @@ } }, "@xmldom/xmldom": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.6.tgz", - "integrity": "sha512-HHXP9hskkFQHy8QxxUXkS7946FFIhYVfGqsk0WLwllmexN9x/+R4UBLvurHEuyXRfVEObVR8APuQehykLviwSQ==", + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", "dev": true }, "@yarnpkg/lockfile": { @@ -4507,34 +4507,46 @@ } }, "enzyme-adapter-react-16": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.6.tgz", - "integrity": "sha512-yFlVJCXh8T+mcQo8M6my9sPgeGzj85HSHi6Apgf1Cvq/7EL/J9+1JoJmJsRxZgyTvPMAqOEpRSu/Ii/ZpyOk0g==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.7.tgz", + "integrity": "sha512-LtjKgvlTc/H7adyQcj+aq0P0H07LDL480WQl1gU512IUyaDo/sbOaNDdZsJXYW2XaoPqrLLE9KbZS+X2z6BASw==", "dev": true, "requires": { - "enzyme-adapter-utils": "^1.14.0", - "enzyme-shallow-equal": "^1.0.4", + "enzyme-adapter-utils": "^1.14.1", + "enzyme-shallow-equal": "^1.0.5", "has": "^1.0.3", - "object.assign": "^4.1.2", - "object.values": "^1.1.2", - "prop-types": "^15.7.2", + "object.assign": "^4.1.4", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", "react-is": "^16.13.1", "react-test-renderer": "^16.0.0-0", "semver": "^5.7.0" + }, + "dependencies": { + "enzyme-shallow-equal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.5.tgz", + "integrity": "sha512-i6cwm7hN630JXenxxJFBKzgLC3hMTafFQXflvzHgPmDhOBhxUWDe8AeRv1qp2/uWJ2Y8z5yLWMzmAfkTOiOCZg==", + "dev": true, + "requires": { + "has": "^1.0.3", + "object-is": "^1.1.5" + } + } } }, "enzyme-adapter-utils": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.0.tgz", - "integrity": "sha512-F/z/7SeLt+reKFcb7597IThpDp0bmzcH1E9Oabqv+o01cID2/YInlqHbFl7HzWBl4h3OdZYedtwNDOmSKkk0bg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.1.tgz", + "integrity": "sha512-JZgMPF1QOI7IzBj24EZoDpaeG/p8Os7WeBZWTJydpsH7JRStc7jYbHE4CmNQaLqazaGFyLM8ALWA3IIZvxW3PQ==", "dev": true, "requires": { "airbnb-prop-types": "^2.16.0", - "function.prototype.name": "^1.1.3", + "function.prototype.name": "^1.1.5", "has": "^1.0.3", - "object.assign": "^4.1.2", - "object.fromentries": "^2.0.3", - "prop-types": "^15.7.2", + "object.assign": "^4.1.4", + "object.fromentries": "^2.0.5", + "prop-types": "^15.8.1", "semver": "^5.7.1" } }, @@ -9048,12 +9060,12 @@ "dev": true }, "jasmine-reporters": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.5.0.tgz", - "integrity": "sha512-J69peyTR8j6SzvIPP6aO1Y00wwCqXuIvhwTYvE/di14roCf6X3wDZ4/cKGZ2fGgufjhP2FKjpgrUIKjwau4e/Q==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.5.2.tgz", + "integrity": "sha512-qdewRUuFOSiWhiyWZX8Yx3YNQ9JG51ntBEO4ekLQRpktxFTwUHy24a86zD/Oi2BRTKksEdfWQZcQFqzjqIkPig==", "dev": true, "requires": { - "@xmldom/xmldom": "^0.7.3", + "@xmldom/xmldom": "^0.8.5", "mkdirp": "^1.0.4" }, "dependencies": { @@ -10610,9 +10622,9 @@ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "moment-timezone": { - "version": "0.5.41", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.41.tgz", - "integrity": "sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg==", + "version": "0.5.43", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", + "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", "requires": { "moment": "^2.29.4" } @@ -15886,6 +15898,15 @@ "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, + "moment-timezone": { + "version": "0.5.41", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.41.tgz", + "integrity": "sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg==", + "dev": true, + "requires": { + "moment": "^2.29.4" + } + }, "prop-types": { "version": "15.6.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz", @@ -15951,9 +15972,9 @@ } }, "superdesk-ui-framework": { - "version": "3.0.54", - "resolved": "https://registry.npmjs.org/superdesk-ui-framework/-/superdesk-ui-framework-3.0.54.tgz", - "integrity": "sha512-wWtx2AEJUEShU7v60KteMcPW+vfP0iI3KDnWxnBxsNm6Y7T/nT2ROrd2U5dM/fjNt41jJEiP+AD7mN3ykX8Q4g==", + "version": "3.0.59", + "resolved": "https://registry.npmjs.org/superdesk-ui-framework/-/superdesk-ui-framework-3.0.59.tgz", + "integrity": "sha512-FuXyJNGVE970jlHWm0vD1Cr9QGLEfjONPaPfNSAKQZHW1f//By2VERgPi8TFv1kTdZOlXcOP6vFhnw/OR/Z6Nw==", "requires": { "@material-ui/lab": "^4.0.0-alpha.56", "@popperjs/core": "^2.4.0", @@ -15976,9 +15997,9 @@ }, "dependencies": { "@types/node": { - "version": "14.18.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.54.tgz", - "integrity": "sha512-uq7O52wvo2Lggsx1x21tKZgqkJpvwCseBBPtX/nKQfpVlEsLOb11zZ1CRsWUKvJF0+lzuA9jwvA7Pr2Wt7i3xw==" + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" }, "enzyme-adapter-react-16": { "version": "1.15.7", diff --git a/package.json b/package.json index 8174889e4..e5d8ed798 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "dependencies": { "dompurify": "^1.0.11", "moment": "^2.29.4", - "moment-timezone": "^0.5.41", + "moment-timezone": "^0.5.43", "nominatim-browser": "~2.0.2", "react-bootstrap": "0.32.1", "react-debounce-input": "3.2.0", @@ -34,13 +34,13 @@ "redux-logger": "~3.0.6", "reselect": "~3.0.1", "rrule": "~2.2.9", - "superdesk-ui-framework": "^3.0.54", + "superdesk-ui-framework": "^3.0.59", "ts-loader": "3.5.0", "typescript": "~4.9.5", "whatwg-fetch": "~2.0.4" }, "devDependencies": { - "@types/jasmine": "^3.5.11", + "@types/jasmine": "^3.10.7", "@types/lodash": "4.14.117", "@types/react": "16.8.23", "@types/react-dom": "16.8.0", @@ -50,11 +50,11 @@ "btoa": "^1.1.2", "cheerio": "1.0.0-rc.10", "enzyme": "~3.11.0", - "enzyme-adapter-react-16": "^1.15.5", + "enzyme-adapter-react-16": "^1.15.7", "eslint": "6.6.0", "eslint-plugin-react": "7.16.0", "jasmine": "^2.99.0", - "jasmine-reporters": "^2.3.0", + "jasmine-reporters": "^2.5.2", "karma": "^2.0.0", "karma-chrome-launcher": "2.2.0", "karma-cli": "^1.0.1", diff --git a/server/features/events.feature b/server/features/events.feature index f7bd48396..30505f33d 100644 --- a/server/features/events.feature +++ b/server/features/events.feature @@ -1376,4 +1376,32 @@ Feature: Events { "assignment_id": null } - """ \ No newline at end of file + """ + + @auth + Scenario: Create events without a timezone + When we post to "/events" + """ + [{ + "guid": "event1", + "name": "No timezone defined", + "dates": { + "start": "2029-11-21T12:00:00.000Z", + "end": "2029-11-21T14:00:00.000Z" + } + }] + """ + Then we get OK response + When we post to "/events" + """ + [{ + "guid": "event2", + "name": "null timezone", + "dates": { + "start": "2029-11-21T12:00:00.000Z", + "end": "2029-11-21T14:00:00.000Z", + "tz": null + } + }] + """ + Then we get OK response \ No newline at end of file diff --git a/server/features/search_events.feature b/server/features/search_events.feature index e2aad22fd..71d98519d 100644 --- a/server/features/search_events.feature +++ b/server/features/search_events.feature @@ -68,7 +68,8 @@ Feature: Event Search "world_region": "Asia", "country": "" } - ] + ], + "priority": 2 }, { "guid": "event_786", @@ -87,7 +88,8 @@ Feature: Event Search "end": "2016-01-03T00:00:00+0000" }, "subject": [{"qcode": "test qcode 2", "name": "test name"}], - "lock_session": "ident1" + "lock_session": "ident1", + "priority": 7 } ] """ @@ -210,6 +212,16 @@ Feature: Event Search {"_id": "event_456"} ]} """ + When we get "/events_planning_search?repo=events&only_future=false&priority=2,7" + Then we get list with 2 items + """ + {"_items": [ + {"_id": "event_456"}, + {"_id": "event_786"} + ]} + """ + When we get "/events_planning_search?repo=events&only_future=false&priority=1" + Then we get list with 0 items @auth Scenario: Search by event specific parameters diff --git a/server/features/search_planning.feature b/server/features/search_planning.feature index 7ff5e5946..b232d3a2b 100644 --- a/server/features/search_planning.feature +++ b/server/features/search_planning.feature @@ -111,7 +111,8 @@ Feature: Planning Search } } ], - "urgency": 2 + "urgency": 2, + "priority": 2 }, { "guid": "planning_3", @@ -136,6 +137,7 @@ Feature: Planning Search } ], "urgency": 2, + "priority": 7, "featured": false }, { @@ -301,6 +303,16 @@ Feature: Planning Search {"_id": "planning_6"} ]} """ + When we get "/events_planning_search?repo=planning&only_future=false&priority=2,7" + Then we get list with 2 items + """ + {"_items": [ + {"_id": "planning_2"}, + {"_id": "planning_3"} + ]} + """ + When we get "/events_planning_search?repo=planning&only_future=false&priority=1" + Then we get list with 0 items @auth Scenario: Search by planning specific parameters diff --git a/server/planning/content_profiles/profiles/coverage.py b/server/planning/content_profiles/profiles/coverage.py index fd21d0e29..360c39754 100644 --- a/server/planning/content_profiles/profiles/coverage.py +++ b/server/planning/content_profiles/profiles/coverage.py @@ -29,6 +29,7 @@ class CoverageSchema(BaseSchema): xmp_file = schema.DictField() no_content_linking = BooleanField() scheduled_updates = schema.ListField() + priority = schema.IntegerField() DEFAULT_COVERAGE_PROFILE = { @@ -73,6 +74,7 @@ class CoverageSchema(BaseSchema): "headline": {"enabled": False}, "keyword": {"enabled": False}, "files": {"enabled": False}, + "priority": {"enabled": False}, # Requires `PLANNING_LINK_UPDATES_TO_COVERAGES` enabled in config "no_content_linking": {"enabled": False}, }, diff --git a/server/planning/content_profiles/profiles/event.py b/server/planning/content_profiles/profiles/event.py index 44f77860a..ab3ade91a 100644 --- a/server/planning/content_profiles/profiles/event.py +++ b/server/planning/content_profiles/profiles/event.py @@ -51,6 +51,7 @@ class EventSchema(BaseSchema): invitation_details = TextField(field_type="multi_line") accreditation_info = TextField(field_type="single_line") accreditation_deadline = DateTimeField() + priority = schema.IntegerField() DEFAULT_EVENT_PROFILE = { @@ -110,6 +111,11 @@ class EventSchema(BaseSchema): "group": "description", "index": 8, }, + "priority": { + "enabled": False, + "group": "description", + "index": 9, + }, # Location Group "location": { "enabled": True, diff --git a/server/planning/content_profiles/profiles/planning.py b/server/planning/content_profiles/profiles/planning.py index 7c2a3727f..70a52ecfc 100644 --- a/server/planning/content_profiles/profiles/planning.py +++ b/server/planning/content_profiles/profiles/planning.py @@ -34,6 +34,7 @@ class PlanningSchema(BaseSchema): slugline = StringField(required=True) subject = subjectField urgency = schema.IntegerField() + priority = schema.IntegerField() custom_vocabularies = schema.ListField() associated_event = schema.NoneField() coverages = schema.ListField() @@ -140,6 +141,7 @@ class PlanningSchema(BaseSchema): "group": "coverages", "index": 1, }, + "priority": {"enabled": False, "group": "details", "index": 8}, }, "schema": dict(PlanningSchema), # type: ignore "groups": { diff --git a/server/planning/events/events.py b/server/planning/events/events.py index c6152fc54..4b329a01a 100644 --- a/server/planning/events/events.py +++ b/server/planning/events/events.py @@ -17,6 +17,7 @@ import copy import pytz import re +from datetime import timedelta from eve.methods.common import resolve_document_etag from eve.utils import config, date_to_str from flask import current_app as app @@ -865,7 +866,8 @@ def setRecurringMode(event): def overwrite_event_expiry_date(event): if "expiry" in event: - event["expiry"] = event["dates"]["end"] + expiry_minutes = app.settings.get("PLANNING_EXPIRY_MINUTES", None) + event["expiry"] = event["dates"]["end"] + timedelta(minutes=expiry_minutes or 0) def generate_recurring_events(event): diff --git a/server/planning/events/events_schema.py b/server/planning/events/events_schema.py index 6cf973102..731138208 100644 --- a/server/planning/events/events_schema.py +++ b/server/planning/events/events_schema.py @@ -90,6 +90,7 @@ }, }, "links": {"type": "list", "nullable": True}, + "priority": metadata_schema["priority"], # NewsML-G2 Event properties See IPTC-G2-Implementation_Guide 15.4.3 "dates": { "type": "dict", @@ -102,7 +103,10 @@ "type": "datetime", "nullable": True, }, - "tz": {"type": "string"}, + "tz": { + "type": "string", + "nullable": True, + }, "end_tz": {"type": "string"}, "all_day": {"type": "boolean"}, "no_end_time": {"type": "boolean"}, @@ -254,6 +258,7 @@ "nullable": True, "mapping": { "type": "object", + "dynamic": False, "properties": { "qcode": not_analyzed, "name": not_analyzed, diff --git a/server/planning/events/events_tests.py b/server/planning/events/events_tests.py index ceaa38164..7da297ccf 100644 --- a/server/planning/events/events_tests.py +++ b/server/planning/events/events_tests.py @@ -513,6 +513,7 @@ def test_planning_item_is_published_with_events(self): } ], ) + now = utcnow() get_resource_service("events_post").post( [ { @@ -530,4 +531,4 @@ def test_planning_item_is_published_with_events(self): planning_item = planning_service.find_one(req=None, _id=planning_id[0]) self.assertEqual(len([planning_item]), 1) self.assertEqual(planning_item.get("state"), "scheduled") - self.assertEqual(planning_item.get("versionposted"), utcnow()) + self.assertEqual(planning_item.get("versionposted"), now) diff --git a/server/planning/feed_parsers/onclusive.py b/server/planning/feed_parsers/onclusive.py index 8578f75fc..dc35cf3e7 100644 --- a/server/planning/feed_parsers/onclusive.py +++ b/server/planning/feed_parsers/onclusive.py @@ -12,7 +12,7 @@ CONTENT_STATE, ) from superdesk.errors import ParserError -from superdesk.utc import utcnow +from superdesk.utc import utcnow, local_to_utc from planning.common import POST_STATE from flask import current_app as app @@ -51,33 +51,36 @@ def can_parse(self, content): return False def parse(self, content, provider=None): - try: - all_events = [] - for event in content: - guid = "urn:onclusive:{}".format(event["itemId"]) + all_events = [] + for event in content: + logger.info( + "Parsing event id=%s updated=%s deleted=%s", + event["itemId"], + event["lastEditDateUtc"].split(".")[0], + event["deleted"], + ) - item = { - GUID_FIELD: guid, - ITEM_TYPE: CONTENT_TYPE.EVENT, - "state": CONTENT_STATE.INGESTED, - } + guid = "urn:onclusive:{}".format(event["itemId"]) - try: - self.set_occur_status(item) - self.parse_item_meta(event, item) - self.parse_location(event, item) - self.parse_event_details(event, item) - self.parse_category(event, item) - self.parse_contact_info(event, item) - all_events.append(item) - except EmbargoedException: - logger.info("Ignoring embargoed event %s", event["itemId"]) - except (KeyError, IndexError, TypeError) as error: - logger.exception("error %s ingesting event %s", error, event) - return all_events - - except Exception as ex: - raise ParserError.parseMessageError(ex, provider) + item = { + GUID_FIELD: guid, + ITEM_TYPE: CONTENT_TYPE.EVENT, + "state": CONTENT_STATE.INGESTED, + } + + try: + self.set_occur_status(item) + self.parse_item_meta(event, item) + self.parse_location(event, item) + self.parse_event_details(event, item) + self.parse_category(event, item) + self.parse_contact_info(event, item) + all_events.append(item) + except EmbargoedException: + logger.info("Ignoring embargoed event %s", event["itemId"]) + except Exception as error: + logger.exception("error %s when parsing event %s", error, event["itemId"], extra=dict(event=event)) + return all_events def set_occur_status(self, item): eocstat_map = get_resource_service("vocabularies").find_one(req=None, _id="eventoccurstatus") @@ -96,8 +99,8 @@ def set_occur_status(self, item): def parse_item_meta(self, event, item): item["pubstatus"] = POST_STATE.CANCELLED if event.get("deleted") else POST_STATE.USABLE - item["versioncreated"] = self.datetime(event["lastEditDate"]) - item["firstcreated"] = self.datetime(event["createdDate"]) + item["versioncreated"] = self.server_datetime(event["lastEditDate"], event.get("lastEditDateUtc")) + item["firstcreated"] = self.server_datetime(event["createdDate"], event.get("createdDateUtc")) item["name"] = ( event["summary"] if (event["summary"] is not None and event["summary"] != "") else event["description"] ) @@ -106,7 +109,8 @@ def parse_item_meta(self, event, item): ) item["links"] = [event[key] for key in ("website", "website2") if event.get(key)] - item["language"] = event.get("locale") or self.default_locale + if event.get("locale"): + item["language"] = event["locale"].split("-")[0] if event.get("embargoTime") and event.get("timezone") and event["timezone"].get("timezoneOffset"): tz = datetime.timezone(datetime.timedelta(hours=event["timezone"]["timezoneOffset"])) embargoed = datetime.datetime.fromisoformat(event["embargoTime"]).replace(tzinfo=tz) @@ -116,13 +120,13 @@ def parse_item_meta(self, event, item): def parse_event_details(self, event, item): if event.get("time"): start_date = self.datetime(event["startDate"], event.get("time"), event["timezone"]) - end_date = self.datetime(event["endDate"], "23:59:59", event["timezone"]) + end_date = self.datetime(event["endDate"], timezone=event["timezone"]) tz = self.parse_timezone(start_date, event) item["dates"] = dict( start=start_date, - end=end_date, - tz=tz, + end=max(start_date, end_date), no_end_time=True, + tz=tz, ) else: item["dates"] = dict( @@ -133,23 +137,33 @@ def parse_event_details(self, event, item): def parse_timezone(self, start_date, event): if event.get("timezone"): - timezones = app.config.get("ONCLUSIVE_TIMEZONES", self.ONCLUSIVE_TIMEZONES) + pytz.common_timezones + timezones = ( + app.config.get("ONCLUSIVE_TIMEZONES", self.ONCLUSIVE_TIMEZONES) + + pytz.common_timezones + + pytz.all_timezones + ) for tzname in timezones: try: - date = start_date.astimezone(pytz.timezone(tzname)) + tz = pytz.timezone(tzname) + date = start_date.astimezone(tz) except pytz.exceptions.UnknownTimeZoneError: logger.error("Unknown Timezone %s", tzname) continue abbr = date.strftime("%Z") - if abbr == event["timezone"]["timezoneAbbreviation"]: + offset = date.utcoffset().total_seconds() / 3600 + if abbr == event["timezone"]["timezoneAbbreviation"] and offset == event["timezone"]["timezoneOffset"]: return tzname else: - logger.warning("Could not find timezone for %s", event["timezone"]["timezoneAbbreviation"]) + logger.warning( + "Could not find timezone for %s event %s", + event["timezone"]["timezoneAbbreviation"], + event["itemId"], + ) def parse_location(self, event, item): - if event.get("venue") and event.get("venueData"): + if event.get("venue"): try: - venue_data = event["venueData"][0] + venue_data = event.get("venueData", [])[0] except (IndexError, KeyError): venue_data = {} item["location"] = [ @@ -159,6 +173,11 @@ def parse_location(self, event, item): "address": self.parse_address(event), } ] + if venue_data.get("locationLon") or venue_data.get("locationLat"): + item["location"][0].setdefault( + "location", {"lat": venue_data.get("locationLat"), "lon": venue_data.get("locationLon")} + ) + elif event.get("countryName"): item["location"] = [ { @@ -210,6 +229,21 @@ def datetime(self, date, time=None, timezone=None, tzinfo=None): parsed = parsed.replace(hour=parsed_time.hour, minute=parsed_time.minute, second=parsed_time.second) return parsed.replace(microsecond=0).astimezone(datetime.timezone.utc) + def server_datetime(self, date, date_utc=None): + """Convert datetime from server timezone to utc. + + Eventually this will be in utc, so make it configurable. + """ + if date_utc: + return ( + datetime.datetime.fromisoformat(date_utc.split(".")[0]).replace(microsecond=0).replace(tzinfo=pytz.utc) + ) + parsed = datetime.datetime.fromisoformat(date.split(".")[0]).replace(microsecond=0) + timezone = app.config.get("ONCLUSIVE_SERVER_TIMEZONE", "Europe/London") + if timezone: + return local_to_utc(timezone, parsed) + return parsed.replace(tzinfo=pytz.utc) + def parse_contact_info(self, event, item): for contact_info in event.get("pressContacts"): item.setdefault("event_contact_info", []) diff --git a/server/planning/feed_parsers/onclusive_sample.json b/server/planning/feed_parsers/onclusive_sample.json index 614a82b0d..fa499611b 100644 --- a/server/planning/feed_parsers/onclusive_sample.json +++ b/server/planning/feed_parsers/onclusive_sample.json @@ -13,7 +13,7 @@ "timezoneName": "(EDT) Eastern Daylight Savings Time", "timezoneOffset": -4.00 }, - "venue": "One King West Hotel & Residence, 1 King St W, Toronto", + "venue": "Karuizawa", "tbcVenue": false, "venueData": [ { @@ -46,8 +46,10 @@ "plannedBy": [ 4708 ], - "createdDate": "2021-05-04T21:19:10.2", - "lastEditDate": "2022-05-10T13:14:34.873", + "createdDate": "2021-05-04T22:19:10.2", + "createdDateUtc": "2021-05-04T20:19:10.2", + "lastEditDate": "2022-05-10T15:14:34.873", + "lastEditDateUtc": "2022-05-10T12:14:34.873", "deleted": false, "deletionDate": null, "website": "https://www.canadianinstitute.com/anti-money-laundering-financial-crime/", @@ -58,7 +60,7 @@ "linkedInPage": "", "regionId": 0, "countryId": 38, - "countryName": "Canada", + "countryName": "Japan", "indicator": null, "period": null, "pressContacts": [ diff --git a/server/planning/feed_parsers/onclusive_tests.py b/server/planning/feed_parsers/onclusive_tests.py index b56f9d62e..120933ab6 100644 --- a/server/planning/feed_parsers/onclusive_tests.py +++ b/server/planning/feed_parsers/onclusive_tests.py @@ -38,7 +38,12 @@ def setUp(self): self.parse("onclusive_sample.json") def test_content(self): - item = OnclusiveFeedParser().parse([self.data])[0] + with self.assertLogs("planning", level=logging.INFO) as logger: + item = OnclusiveFeedParser().parse([self.data])[0] + self.assertIn( + "INFO:planning.feed_parsers.onclusive:Parsing event id=4112034 updated=2022-05-10T12:14:34 deleted=False", + logger.output, + ) item["subject"].sort(key=lambda i: i["name"]) expected_subjects = [ {"name": "Law & Order", "qcode": "88", "scheme": "onclusive_categories"}, @@ -56,26 +61,27 @@ def test_content(self): self.assertEqual(item[GUID_FIELD], "urn:onclusive:4112034") self.assertEqual(item[ITEM_TYPE], CONTENT_TYPE.EVENT) self.assertEqual(item["state"], CONTENT_STATE.INGESTED) - self.assertEqual(item["firstcreated"], datetime.datetime(2021, 5, 4, 21, 19, 10, tzinfo=datetime.timezone.utc)) + self.assertEqual(item["firstcreated"], datetime.datetime(2021, 5, 4, 20, 19, 10, tzinfo=datetime.timezone.utc)) self.assertEqual( - item["versioncreated"], datetime.datetime(2022, 5, 10, 13, 14, 34, tzinfo=datetime.timezone.utc) + item["versioncreated"], datetime.datetime(2022, 5, 10, 12, 14, 34, tzinfo=datetime.timezone.utc) ) self.assertEqual(item["occur_status"]["qcode"], "eocstat:eos5") - self.assertEqual(item["language"], "en-CA") + self.assertEqual(item["language"], "en") self.assertIn("https://www.canadianinstitute.com/anti-money-laundering-financial-crime/", item["links"]) self.assertEqual(item["dates"]["start"], datetime.datetime(2022, 6, 15, 10, 30, tzinfo=datetime.timezone.utc)) - self.assertEqual(item["dates"]["end"], datetime.datetime(2022, 6, 16, 3, 59, 59, tzinfo=datetime.timezone.utc)) + self.assertEqual(item["dates"]["end"], datetime.datetime(2022, 6, 15, 10, 30, tzinfo=datetime.timezone.utc)) self.assertEqual(item["dates"]["tz"], "US/Eastern") self.assertEqual(item["dates"]["no_end_time"], True) self.assertEqual(item["name"], "Annual Forum on Anti-Money Laundering and Financial Crime") self.assertEqual(item["definition_short"], "") - self.assertEqual(item["location"][0]["name"], "One King West Hotel & Residence, 1 King St W, Toronto") - self.assertEqual(item["location"][0]["address"]["country"], "Canada") + self.assertEqual(item["location"][0]["name"], "Karuizawa") + self.assertEqual(item["location"][0]["address"]["country"], "Japan") + self.assertEqual(item["location"][0]["location"], {"lat": 43.64894, "lon": -79.378086}) self.assertEqual(1, len(item["event_contact_info"])) self.assertIsInstance(item["event_contact_info"][0], bson.ObjectId) @@ -112,6 +118,32 @@ def test_unknown_timezone(self): OnclusiveFeedParser().parse([self.data]) self.assertIn("ERROR:planning.feed_parsers.onclusive:Unknown Timezone FOO", logger.output) + def test_cst_timezone(self): + data = self.data.copy() + data.update( + { + "startDate": "2023-04-18T00:00:00.0000000", + "endDate": "2023-04-18T00:00:00.0000000", + "time": "10:00", + "timezone": { + "timezoneID": 24, + "timezoneAbbreviation": "CST", + "timezoneName": "(CST) China Standard Time : Beijing, Taipei", + "timezoneOffset": 8.00, + }, + } + ) + item = OnclusiveFeedParser().parse([data])[0] + self.assertEqual( + { + "start": datetime.datetime(2023, 4, 18, 2, tzinfo=datetime.timezone.utc), + "end": datetime.datetime(2023, 4, 18, 2, tzinfo=datetime.timezone.utc), + "no_end_time": True, + "tz": "Asia/Macau", + }, + item["dates"], + ) + def test_embargoed(self): data = self.data.copy() data["embargoTime"] = "2022-12-07T09:00:00" @@ -136,3 +168,22 @@ def test_embargoed(self): utcnow_mock.return_value = datetime.datetime.fromisoformat("2022-12-07T18:00:00+00:00") parsed = OnclusiveFeedParser().parse([data]) self.assertEqual(1, len(parsed)) + + def test_timezone_ambigous_time_error(self): + data = self.data.copy() + data.update( + { + "startDate": "2023-10-27T00:00:00.0000000", + "time": "08:30", + "timezone": { + "timezoneID": 27, + "timezoneAbbreviation": "JST", + "timezoneName": "(JST) Japan Standard Time : Tokyo", + "timezoneOffset": 9.00, + "timezoneIdentity": None, + }, + } + ) + + item = OnclusiveFeedParser().parse([data])[0] + assert item["dates"]["tz"] == "Asia/Tokyo" diff --git a/server/planning/feeding_services/onclusive_api_service.py b/server/planning/feeding_services/onclusive_api_service.py index d7a444151..66e116e59 100644 --- a/server/planning/feeding_services/onclusive_api_service.py +++ b/server/planning/feeding_services/onclusive_api_service.py @@ -2,8 +2,8 @@ import requests from typing import Optional -from datetime import timedelta -from flask import current_app as app, json +from datetime import timedelta, datetime +from flask import current_app as app from flask_babel import lazy_gettext from superdesk.io.registry import register_feeding_service_parser from superdesk.io.feeding_services.http_base_service import HTTPFeedingServiceBase @@ -73,10 +73,10 @@ def _update(self, provider, update): :type update: dict :return: a list of events which can be saved. """ - URL = provider["config"]["url"] - LIMIT = 100 - MAX_OFFSET = int(app.config.get("ONCLUSIVE_MAX_OFFSET", 100000)) + BASE_URL = provider["config"]["url"] + LIMIT = 2000 self.session = requests.Session() + self.language = "en-CA" # make sure there is some default parser = self.get_feed_parser(provider) update["tokens"] = provider.get("tokens") or {} with timer("onclusive:update"): @@ -87,59 +87,53 @@ def _update(self, provider, update): second=0 ) # next time start from here, onclusive api does not use seconds if update["tokens"].get("import_finished"): - url = urljoin(URL, "/api/v2/events/latest") - start = update["tokens"]["import_finished"] - timedelta( - hours=1 - ) # add 1h buffer to avoid missing events + # 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)) + url = urljoin(BASE_URL, "/api/v2/events/latest") + start = update["tokens"]["next_start"] - timedelta( + hours=3, # add a buffer, also not sure about timezone there + ) + update["tokens"]["next_start"] = update["last_updated"] logger.info("Fetching updates since %s", start.isoformat()) - start_offset = 0 params = dict( date=start.strftime("%Y%m%d"), time=start.strftime("%H%M"), limit=LIMIT, ) + iterations = range(0, LIMIT, LIMIT) + iterations_param = "offset" else: + iterations_param = "date" days = int(provider["config"].get("days_to_ingest") or 365) logger.info("Fetching %d days", days) - url = urljoin(URL, "/api/v2/events/between") - start = update["tokens"].get("start_date") or update["last_updated"] - update["tokens"]["start_date"] = start # store for next time - end = start + timedelta(days=days) - start_offset = ( - update["tokens"].get("start_offset") or 0 - ) # allow to continue in case this won't fininsh in single run - if start_offset: - logger.info("Continuing from %d", start_offset) + url = urljoin(BASE_URL, "/api/v2/events/date") + update["tokens"].setdefault("start_date", update["last_updated"]) # keep for next round + update["tokens"].setdefault("next_start", update["last_updated"]) # after import continue from start + start_date = update["tokens"]["start_date"].date() params = dict( - startDate=start.strftime("%Y%m%d"), - endDate=end.strftime("%Y%m%d"), limit=LIMIT, ) + processed_date = update["tokens"].get(iterations_param, "") + iterations = ( + date + for date in ((start_date + timedelta(days=i)).strftime("%Y%m%d") for i in range(0, days)) + if date > processed_date # when continuing skip previously ingested days + ) logger.info("ingest from onclusive %s with params %s", url, params) try: - last_updated = None - for offset in range(start_offset, MAX_OFFSET, LIMIT): - params["offset"] = offset - logger.debug("params %s", params) + for i in iterations: + params[iterations_param] = i + logger.info("Onclusive PARAMS %s", params) content = self._fetch(url, params, provider, update["tokens"]) - if not content: - logger.info("done ingesting offset=%d last_updated=%s", offset, last_updated) - if last_updated: - update["tokens"]["import_finished"] = last_updated - break items = parser.parse(content, provider) + logger.info("Onclusive returned %d items", len(items)) for item in items: - if item.get("versioncreated"): - last_updated = ( - max(last_updated, item["versioncreated"]) if last_updated else item["versioncreated"] - ) + item.setdefault("language", self.language) yield items - update["tokens"]["start_offset"] = offset - else: - logger.warning("some items were not fetched due to the limit") + update["tokens"][iterations_param] = i + update["tokens"].setdefault("import_finished", utcnow()) except SoftTimeLimitExceeded: logger.warning("stopped due to time limit, tokens=%s", update["tokens"]) - # let it finish the current job and update the start_offset for next time def _fetch(self, url, params, provider, tokens): for i in range(5): @@ -181,6 +175,7 @@ def credentials(self, provider, tokens) -> Optional[str]: data = resp.json() if data.get("refreshToken"): tokens[REFRESH_TOKEN_KEY] = data["refreshToken"] + self.set_language(data) if data.get("token"): self.token = data["token"] return self.token @@ -204,7 +199,14 @@ def renew_token(self, provider, tokens): if new_token.get("refreshToken"): tokens[REFRESH_TOKEN_KEY] = new_token["refreshToken"] self.token = new_token["token"] + self.set_language(new_token) return self.token + def set_language(self, data): + if data.get("productId") and data["productId"] == 10: + self.language = "fr-CA" + else: + self.language = "en-CA" + register_feeding_service_parser(OnclusiveApiService.NAME, OnclusiveApiService.FeedParser) diff --git a/server/planning/feeding_services/onclusive_api_service_tests.py b/server/planning/feeding_services/onclusive_api_service_tests.py index e3a032fd0..243864d02 100644 --- a/server/planning/feeding_services/onclusive_api_service_tests.py +++ b/server/planning/feeding_services/onclusive_api_service_tests.py @@ -1,10 +1,8 @@ from planning.feed_parsers.onclusive import OnclusiveFeedParser -from planning.tests import TestCase from .onclusive_api_service import OnclusiveApiService from unittest.mock import MagicMock -from datetime import datetime +from datetime import datetime, timedelta -import os import flask import unittest import requests_mock @@ -21,6 +19,7 @@ def setUp(self) -> None: def test_update(self): event = {"versioncreated": datetime.fromisoformat("2023-03-01T08:00:00")} with self.app.app_context(): + now = datetime.utcnow() service = OnclusiveApiService() service.get_feed_parser = MagicMock(return_value=parser) parser.parse.return_value = [event] @@ -39,14 +38,20 @@ def test_update(self): json={ "token": "tok", "refreshToken": "refresh", + "productId": 10, }, ) - m.get("https://api.abc.com/api/v2/events/between?offset=0", json=[{}]) # first returns an item - m.get("https://api.abc.com/api/v2/events/between?offset=100", json=[]) # second will make it stop - list(service._update(provider, updates)) + m.get( + "https://api.abc.com/api/v2/events/date?date={}".format(now.strftime("%Y%m%d")), + json=[{"versioncreated": event["versioncreated"].isoformat()}], + ) # first returns an item + m.get("https://api.abc.com/api/v2/events/date", json=[]) # ones won't + items = list(service._update(provider, updates)) self.assertIn("tokens", updates) self.assertEqual("refresh", updates["tokens"]["refreshToken"]) - self.assertEqual(event["versioncreated"], updates["tokens"]["import_finished"]) + self.assertIn("import_finished", updates["tokens"]) + self.assertEqual(updates["last_updated"], updates["tokens"]["next_start"]) + self.assertEqual("fr-CA", items[0][0]["language"]) provider.update(updates) updates = {} diff --git a/server/planning/io/ingest_rule_handler.py b/server/planning/io/ingest_rule_handler.py index b593cf7fb..0a0462210 100644 --- a/server/planning/io/ingest_rule_handler.py +++ b/server/planning/io/ingest_rule_handler.py @@ -75,18 +75,14 @@ def apply_rule(self, rule: Dict[str, Any], ingest_item: Dict[str, Any], routing_ if updates is not None: ingest_item.update(updates) - if attributes.get("autopost", False) and (updates is None or not self._is_original_posted(ingest_item)): - # Only autopost if: - # * The original has not been posted yet - # * Or there are no updates applied (from assigning Calendar/Agenda to the item) - # because updating the Calendar/Agenda will automatically re-post the item for us + if attributes.get("autopost", False): self.process_autopost(ingest_item) def _is_original_posted(self, ingest_item: Dict[str, Any]): service = get_resource_service("events" if ingest_item[ITEM_TYPE] == CONTENT_TYPE.EVENT else "planning") original = service.find_one(req=None, _id=ingest_item.get(config.ID_FIELD)) - return original.get("pubstatus") in [POST_STATE.USABLE, POST_STATE.CANCELLED] + return original is not None and original.get("pubstatus") in [POST_STATE.USABLE, POST_STATE.CANCELLED] def add_event_calendars(self, ingest_item: Dict[str, Any], attributes: Dict[str, Any]) -> Optional[Dict[str, Any]]: """Add Event Calendars from Routing Rule Action onto the ingested item""" @@ -169,9 +165,13 @@ def add_planning_agendas(self, ingest_item: Dict[str, Any], attributes: Dict[str def process_autopost(self, ingest_item: Dict[str, Any]): """Automatically post this item""" - logger.info(ingest_item) + if self._is_original_posted(ingest_item): + # No need to autopost this item + # As the original is already posted + # And any updates from ingest should automatically re-post this item + return + item_id = ingest_item.get(config.ID_FIELD) - logger.info(f"Posting item {item_id}") update_post_item( { "pubstatus": ingest_item.get("pubstatus") or POST_STATE.USABLE, diff --git a/server/planning/planning/planning.py b/server/planning/planning/planning.py index 74b66d532..3204a9ec0 100644 --- a/server/planning/planning/planning.py +++ b/server/planning/planning/planning.py @@ -1428,6 +1428,7 @@ def duplicate_xmp_file(self, coverage): "subject": metadata_schema["subject"], "internal_note": {"type": "string"}, "workflow_status_reason": {"type": "string", "nullable": True}, + "priority": metadata_schema["priority"], }, # end planning dict schema }, # end planning "news_coverage_status": { diff --git a/server/planning/search/eventsplanning_filters.py b/server/planning/search/eventsplanning_filters.py index f78756a11..1f8cf8074 100644 --- a/server/planning/search/eventsplanning_filters.py +++ b/server/planning/search/eventsplanning_filters.py @@ -137,6 +137,7 @@ def cv(qcode_type="string", extra_fields=None): "item_ids": list_strings(), "name": string(), "tz_offset": string(), + "time_zone": string(), "full_text": string(), "anpa_category": list_cvs(), "subject": list_cvs(), diff --git a/server/planning/search/queries/common.py b/server/planning/search/queries/common.py index 659c54a2f..81a075d5e 100644 --- a/server/planning/search/queries/common.py +++ b/server/planning/search/queries/common.py @@ -12,11 +12,9 @@ import logging from datetime import datetime -from flask import current_app as app from eve.utils import str_to_date as _str_to_date, date_to_str from superdesk import get_resource_service -from superdesk.utc import get_timezone_offset, utcnow from superdesk.errors import SuperdeskApiError from superdesk.default_settings import strtobool as _strtobool from superdesk.users.services import current_user_has_privilege @@ -30,13 +28,9 @@ logger = logging.getLogger(__name__) -def get_time_zone(params: Dict[str, Any]): - return params.get("tz_offset") or get_timezone_offset(app.config["DEFAULT_TIMEZONE"], utcnow()) - - def get_date_params(params: Dict[str, Any]): date_filter = (params.get("date_filter") or "").strip().lower() - tz_offset = get_time_zone(params) + time_zone = params.get("time_zone") try: start_date = params.get("start_date") @@ -67,7 +61,7 @@ def get_date_params(params: Dict[str, Any]): logger.exception(e) raise SuperdeskApiError.badRequestError("Invalid value for end date") - return date_filter, start_date, end_date, tz_offset + return date_filter, start_date, end_date, time_zone def str_to_array(arg: Optional[Union[List[str], str]] = None) -> List[str]: @@ -325,16 +319,16 @@ def search_date_non_schedule(params: Dict[str, Any], query: elastic.ElasticQuery if not field_name or field_name == "schedule": return - date_filter, start_date, end_date, tz_offset = get_date_params(params) + date_filter, start_date, end_date, time_zone = get_date_params(params) if not date_filter and not start_date and not end_date: query.filter.append( - elastic.date_range(elastic.ElasticRangeParams(field=field_name, lte="now/d", time_zone=tz_offset)) + elastic.date_range(elastic.ElasticRangeParams(field=field_name, lte="now/d", time_zone=time_zone)) ) else: base_query = elastic.ElasticRangeParams( field=field_name, - time_zone=tz_offset, + time_zone=time_zone, start_of_week=int(params.get("start_of_week") or 0), ) @@ -460,6 +454,13 @@ def search_source(params: Dict[str, Any], query: elastic.ElasticQuery): query.must.append(elastic.terms(field="ingest_provider", values=sources)) +def search_priority(params: Dict[str, Any], query: elastic.ElasticQuery): + priorities = [str(qcode) for qcode in str_to_array(params.get("priority"))] + + if len(priorities): + query.must.append(elastic.terms(field="priority", values=priorities)) + + COMMON_SEARCH_FILTERS: List[Callable[[Dict[str, Any], elastic.ElasticQuery], None]] = [ search_item_ids, search_name, @@ -475,6 +476,7 @@ def search_source(params: Dict[str, Any], query: elastic.ElasticQuery): restrict_items_to_user_only, search_original_creator, search_source, + search_priority, ] @@ -482,6 +484,7 @@ def search_source(params: Dict[str, Any], query: elastic.ElasticQuery): "item_ids", "name", "tz_offset", + "time_zone", "full_text", "anpa_category", "subject", @@ -509,4 +512,5 @@ def search_source(params: Dict[str, Any], query: elastic.ElasticQuery): "sort_field", "original_creator", "source", + "priority", ] diff --git a/server/planning/search/queries/elastic.py b/server/planning/search/queries/elastic.py index adca47082..523b19a3c 100644 --- a/server/planning/search/queries/elastic.py +++ b/server/planning/search/queries/elastic.py @@ -9,12 +9,12 @@ # at https://www.sourcefabric.org/superdesk/license from typing import Any, List, NamedTuple, Dict, Optional, Set +import pytz -from datetime import timedelta +from datetime import timedelta, datetime from flask import current_app as app from eve.utils import str_to_date -from superdesk.utc import get_timezone_offset, utcnow from planning.common import get_start_of_next_week, sanitize_query_text @@ -113,7 +113,7 @@ def __init__( self.lt = lt self.lte = lte self.value_format = value_format - self.time_zone = time_zone if time_zone else get_timezone_offset(app.config["DEFAULT_TIMEZONE"], utcnow()) + self.time_zone = time_zone or app.config.get("DEFAULT_TIMEZONE") self.start_of_week = int(start_of_week or 0) self.date_range = date_range self.date = str_to_date(date) if date else None @@ -201,6 +201,35 @@ def field_range(query: ElasticRangeParams): if query.time_zone: params["time_zone"] = query.time_zone + if query.field in ("dates.start", "dates.end"): + # handle also all day events + # there we get value which is in utc, + # so we first convert it to local timezone + # and then we take only date part of it + local_params = params.copy() + local_params.pop("time_zone", None) + for key in ("gt", "gte", "lt", "lte"): + if local_params.get(key) and "T" in local_params[key] and query.time_zone: + tz = pytz.timezone(query.time_zone) + utc_value = datetime.fromisoformat(local_params[key].replace("+0000", "+00:00")) + local_value = utc_value.astimezone(tz) + local_params[key] = local_value.strftime("%Y-%m-%d") + return { + "bool": { + "should": [ + {"range": {query.field: params}}, + { + "bool": { + "must": [ + {"term": {"dates.all_day": True}}, + {"range": {query.field: local_params}}, + ], + } + }, + ], + }, + } + return {"range": {query.field: params}} @@ -244,7 +273,7 @@ def range_this_week(query: ElasticRangeParams): return field_range( ElasticRangeParams( field=query.field, - time_zone=query.time_zone or app.config["DEFAULT_TIMEZONE"], + time_zone=query.time_zone, value_format=query.value_format, gte=start_of_this_week(query.start_of_week), lt=start_of_next_week(query.start_of_week), @@ -256,7 +285,7 @@ def range_next_week(query: ElasticRangeParams): return field_range( ElasticRangeParams( field=query.field, - time_zone=query.time_zone or app.config["DEFAULT_TIMEZONE"], + time_zone=query.time_zone, value_format=query.value_format, gte=start_of_next_week(query.start_of_week), lt=end_of_next_week(query.start_of_week), diff --git a/server/planning/search/queries/events.py b/server/planning/search/queries/events.py index 54baf46b4..5502a4d1b 100644 --- a/server/planning/search/queries/events.py +++ b/server/planning/search/queries/events.py @@ -12,7 +12,6 @@ from planning.search.queries import elastic from .common import ( - get_time_zone, get_date_params, COMMON_SEARCH_FILTERS, COMMON_PARAMS, @@ -71,7 +70,7 @@ def search_no_calendar_assigned(params: Dict[str, Any], query: elastic.ElasticQu def search_date_today(params: Dict[str, Any], query: elastic.ElasticQuery): if params.get("date_filter") == elastic.DATE_RANGE.TODAY: - time_zone = get_time_zone(params) + time_zone = params.get("time_zone") query.filter.append( elastic.bool_or( @@ -103,7 +102,7 @@ def search_date_today(params: Dict[str, Any], query: elastic.ElasticQuery): def search_date_tomorrow(params: Dict[str, Any], query: elastic.ElasticQuery): if params.get("date_filter") == elastic.DATE_RANGE.TOMORROW: - time_zone = get_time_zone(params) + time_zone = params.get("time_zone") query.filter.append( elastic.bool_or( @@ -135,7 +134,7 @@ def search_date_tomorrow(params: Dict[str, Any], query: elastic.ElasticQuery): def search_date_last_24_hours(params: Dict[str, Any], query: elastic.ElasticQuery): if params.get("date_filter") == elastic.DATE_RANGE.LAST_24: - time_zone = get_time_zone(params) + time_zone = params.get("time_zone") query.filter.append( elastic.bool_or( @@ -163,7 +162,7 @@ def search_date_last_24_hours(params: Dict[str, Any], query: elastic.ElasticQuer def search_date_this_week(params: Dict[str, Any], query: elastic.ElasticQuery): if params.get("date_filter") == elastic.DATE_RANGE.THIS_WEEK: - time_zone = get_time_zone(params) + time_zone = params.get("time_zone") start_of_week = int(params.get("start_of_week") or 0) query.filter.append( @@ -208,7 +207,7 @@ def search_date_this_week(params: Dict[str, Any], query: elastic.ElasticQuery): def search_date_next_week(params: Dict[str, Any], query: elastic.ElasticQuery): if params.get("date_filter") == elastic.DATE_RANGE.NEXT_WEEK: - time_zone = get_time_zone(params) + time_zone = params.get("time_zone") start_of_week = int(params.get("start_of_week") or 0) query.filter.append( @@ -252,17 +251,17 @@ def search_date_next_week(params: Dict[str, Any], query: elastic.ElasticQuery): def search_date_start(params: Dict[str, Any], query: elastic.ElasticQuery): - date_filter, start_date, end_date, tz_offset = get_date_params(params) + date_filter, start_date, end_date, time_zone = get_date_params(params) if not date_filter and start_date and not end_date: query.filter.append( elastic.bool_or( [ elastic.date_range( - elastic.ElasticRangeParams(field="dates.start", gte=start_date, time_zone=tz_offset) + elastic.ElasticRangeParams(field="dates.start", gte=start_date, time_zone=time_zone) ), elastic.date_range( - elastic.ElasticRangeParams(field="dates.end", gte=start_date, time_zone=tz_offset) + elastic.ElasticRangeParams(field="dates.end", gte=start_date, time_zone=time_zone) ), ] ) @@ -270,17 +269,17 @@ def search_date_start(params: Dict[str, Any], query: elastic.ElasticQuery): def search_date_end(params: Dict[str, Any], query: elastic.ElasticQuery): - date_filter, start_date, end_date, tz_offset = get_date_params(params) + date_filter, start_date, end_date, time_zone = get_date_params(params) if not date_filter and not start_date and end_date: query.filter.append( elastic.bool_or( [ elastic.date_range( - elastic.ElasticRangeParams(field="dates.start", lte=end_date, time_zone=tz_offset) + elastic.ElasticRangeParams(field="dates.start", lte=end_date, time_zone=time_zone) ), elastic.date_range( - elastic.ElasticRangeParams(field="dates.end", lte=end_date, time_zone=tz_offset) + elastic.ElasticRangeParams(field="dates.end", lte=end_date, time_zone=time_zone) ), ] ) @@ -288,7 +287,7 @@ def search_date_end(params: Dict[str, Any], query: elastic.ElasticQuery): def search_date_range(params: Dict[str, Any], query: elastic.ElasticQuery): - date_filter, start_date, end_date, tz_offset = get_date_params(params) + date_filter, start_date, end_date, time_zone = get_date_params(params) if not date_filter and start_date and end_date: query.filter.append( @@ -300,11 +299,11 @@ def search_date_range(params: Dict[str, Any], query: elastic.ElasticQuery): elastic.ElasticRangeParams( field="dates.start", gte=start_date, - time_zone=tz_offset, + time_zone=time_zone, ) ), elastic.date_range( - elastic.ElasticRangeParams(field="dates.end", lte=end_date, time_zone=tz_offset) + elastic.ElasticRangeParams(field="dates.end", lte=end_date, time_zone=time_zone) ), ] ), @@ -314,11 +313,11 @@ def search_date_range(params: Dict[str, Any], query: elastic.ElasticQuery): elastic.ElasticRangeParams( field="dates.start", lt=start_date, - time_zone=tz_offset, + time_zone=time_zone, ) ), elastic.date_range( - elastic.ElasticRangeParams(field="dates.end", gt=end_date, time_zone=tz_offset) + elastic.ElasticRangeParams(field="dates.end", gt=end_date, time_zone=time_zone) ), ] ), @@ -329,7 +328,7 @@ def search_date_range(params: Dict[str, Any], query: elastic.ElasticQuery): field="dates.start", gte=start_date, lte=end_date, - time_zone=tz_offset, + time_zone=time_zone, ) ), elastic.date_range( @@ -337,7 +336,7 @@ def search_date_range(params: Dict[str, Any], query: elastic.ElasticQuery): field="dates.end", gte=start_date, lte=end_date, - time_zone=tz_offset, + time_zone=time_zone, ) ), ] @@ -348,12 +347,12 @@ def search_date_range(params: Dict[str, Any], query: elastic.ElasticQuery): def search_date_default(params: Dict[str, Any], query: elastic.ElasticQuery): - date_filter, start_date, end_date, tz_offset = get_date_params(params) + date_filter, start_date, end_date, time_zone = get_date_params(params) only_future = strtobool(params.get("only_future", True)) if not date_filter and not start_date and not end_date and only_future: query.filter.append( - elastic.date_range(elastic.ElasticRangeParams(field="dates.end", gte="now/d", time_zone=tz_offset)) + elastic.date_range(elastic.ElasticRangeParams(field="dates.end", gte="now/d", time_zone=time_zone)) ) diff --git a/server/planning/search/queries/planning.py b/server/planning/search/queries/planning.py index 6ecbe48c4..a17b22e37 100644 --- a/server/planning/search/queries/planning.py +++ b/server/planning/search/queries/planning.py @@ -154,13 +154,13 @@ def search_by_events(params: Dict[str, Any], query: elastic.ElasticQuery): def search_date(params: Dict[str, Any], query: elastic.ElasticQuery): - date_filter, start_date, end_date, tz_offset = get_date_params(params) + date_filter, start_date, end_date, time_zone = get_date_params(params) if date_filter or start_date or end_date: field_name = "_planning_schedule.scheduled" base_query = elastic.ElasticRangeParams( field=field_name, - time_zone=tz_offset, + time_zone=time_zone, start_of_week=int(params.get("start_of_week") or 0), ) @@ -203,7 +203,7 @@ def search_date(params: Dict[str, Any], query: elastic.ElasticQuery): ) query.extra["sort_filter"] = elastic.date_range( - elastic.ElasticRangeParams(field=field_name, gte="now/d", time_zone=tz_offset) + elastic.ElasticRangeParams(field=field_name, gte="now/d", time_zone=time_zone) ) else: query.filter.append(planning_schedule) @@ -211,7 +211,7 @@ def search_date(params: Dict[str, Any], query: elastic.ElasticQuery): def search_date_default(params: Dict[str, Any], query: elastic.ElasticQuery): - date_filter, start_date, end_date, tz_offset = get_date_params(params) + date_filter, start_date, end_date, time_zone = get_date_params(params) only_future = strtobool(params.get("only_future", True)) if not date_filter and not start_date and not end_date and only_future: @@ -220,7 +220,7 @@ def search_date_default(params: Dict[str, Any], query: elastic.ElasticQuery): elastic.ElasticRangeParams( field=field_name, gte="now/d", - time_zone=tz_offset, + time_zone=time_zone, ) )