diff --git a/packages/common/src/core/schemas/internal/events/EventGalleryCardSchema.ts b/packages/common/src/core/schemas/internal/events/EventGalleryCardSchema.ts index 7b3f75eec00..95a516da7cc 100644 --- a/packages/common/src/core/schemas/internal/events/EventGalleryCardSchema.ts +++ b/packages/common/src/core/schemas/internal/events/EventGalleryCardSchema.ts @@ -28,8 +28,8 @@ export const EventGalleryCardSchema: IConfigurationSchema = { }, corners: { type: "string", - enum: ["squared", "rounded"], - default: "squared", + enum: ["square", "round"], + default: "square", }, shadow: { type: "string", diff --git a/packages/common/src/core/types/IHubEvent.ts b/packages/common/src/core/types/IHubEvent.ts index 2258bd5f8d7..a5c6401fe3c 100644 --- a/packages/common/src/core/types/IHubEvent.ts +++ b/packages/common/src/core/types/IHubEvent.ts @@ -158,9 +158,14 @@ export interface IHubEvent readGroupIds: string[]; /** - * A collection of objects containing the ids & types for entities that the event references + * A collection of content ids explicitly referenced by the event */ - references: Array>; + referencedContentIds: string[]; + + /** + * A collection of objects containing the ids & types for entities referenced by the event + */ + referencedContentIdsByType: Array<{ entityId: string; entityType: string }>; /** * The start date of the event diff --git a/packages/common/src/events/_internal/EventSchemaCreate.ts b/packages/common/src/events/_internal/EventSchemaCreate.ts index f55ad09d062..51bae421bba 100644 --- a/packages/common/src/events/_internal/EventSchemaCreate.ts +++ b/packages/common/src/events/_internal/EventSchemaCreate.ts @@ -51,6 +51,14 @@ export const buildSchema = (): IConfigurationSchema => { onlineUrl: { type: "string", }, + referencedContentIds: { + type: "array", + maxItems: 1, + items: { + type: "string", + }, + default: [], + }, }, allOf: [ URL_VALIDATIONS_WHEN_ONLINE_OR_HYBRID, diff --git a/packages/common/src/events/_internal/EventSchemaEdit.ts b/packages/common/src/events/_internal/EventSchemaEdit.ts index 9a9b607dd41..8d089c4f71e 100644 --- a/packages/common/src/events/_internal/EventSchemaEdit.ts +++ b/packages/common/src/events/_internal/EventSchemaEdit.ts @@ -12,14 +12,12 @@ import { FIXED_ONLINE_ATTENDANCE_VALIDATIONS, FIXED_IN_PERSON_ATTENDANCE_VALIDATIONS, } from "./validations"; -import { getDefaultEventDatesAndTimes } from "./getDefaultEventDatesAndTimes"; /** * @private * Builds a schema for creating a new Event that enforces a startDate relative to the user's locale */ export const buildSchema = (): IConfigurationSchema => { - const { startDate: minStartDate } = getDefaultEventDatesAndTimes(); return { required: ["name", "startDate", "endDate", "timeZone"], properties: { @@ -85,6 +83,14 @@ export const buildSchema = (): IConfigurationSchema => { enum: [HubEventCapacityType.Unlimited, HubEventCapacityType.Fixed], default: HubEventCapacityType.Unlimited, }, + referencedContentIds: { + type: "array", + maxItems: 1, + items: { + type: "string", + }, + default: [], + }, summary: ENTITY_SUMMARY_SCHEMA, tags: ENTITY_TAGS_SCHEMA, categories: ENTITY_CATEGORIES_SCHEMA, diff --git a/packages/common/src/events/_internal/EventUiSchemaCreate.ts b/packages/common/src/events/_internal/EventUiSchemaCreate.ts index 4b9ea29338b..7037d280c61 100644 --- a/packages/common/src/events/_internal/EventUiSchemaCreate.ts +++ b/packages/common/src/events/_internal/EventUiSchemaCreate.ts @@ -4,6 +4,7 @@ import { EntityEditorOptions } from "../../core/schemas/internal/EditorOptions"; import { getDatePickerDate } from "../../utils/date/getDatePickerDate"; import { IHubEvent } from "../../core/types/IHubEvent"; import { HubEventAttendanceType } from "../types"; +import { buildReferencedContentSchema } from "./buildReferencedContentSchema"; /** * @private @@ -170,6 +171,11 @@ export const buildUiSchema = async ( ], }, }, + buildReferencedContentSchema( + i18nScope, + context, + `{{${i18nScope}.fields.referencedContent.label:translate}}` + ), ], }; }; diff --git a/packages/common/src/events/_internal/EventUiSchemaEdit.ts b/packages/common/src/events/_internal/EventUiSchemaEdit.ts index f39164cab3d..36063312fbe 100644 --- a/packages/common/src/events/_internal/EventUiSchemaEdit.ts +++ b/packages/common/src/events/_internal/EventUiSchemaEdit.ts @@ -1,12 +1,13 @@ import { IUiSchema, UiSchemaRuleEffects } from "../../core/schemas/types"; import { IArcGISContext } from "../../ArcGISContext"; -import { getDatePickerDate } from "../../utils/date/getDatePickerDate"; import { IHubEvent } from "../../core/types/IHubEvent"; import { getTagItems } from "../../core/schemas/internal/getTagItems"; import { HubEventAttendanceType, HubEventCapacityType } from "../types"; import { getLocationExtent } from "../../core/schemas/internal/getLocationExtent"; import { getLocationOptions } from "../../core/schemas/internal/getLocationOptions"; import { fetchCategoriesUiSchemaElement } from "../../core/schemas/internal/fetchCategoriesUiSchemaElement"; +import { getWellKnownCatalog } from "../../search/wellKnownCatalog"; +import { buildReferencedContentSchema } from "./buildReferencedContentSchema"; /** * @private @@ -19,10 +20,6 @@ export const buildUiSchema = async ( options: Partial, context: IArcGISContext ): Promise => { - const minStartEndDate = getDatePickerDate( - new Date(), - (options as IHubEvent).timeZone - ); return { type: "Layout", elements: [ @@ -401,6 +398,11 @@ export const buildUiSchema = async ( }, ], }, + { + type: "Section", + labelKey: `${i18nScope}.sections.referencedContent.label`, + elements: [buildReferencedContentSchema(i18nScope, context)], + }, { type: "Section", labelKey: `${i18nScope}.sections.discoverability.label`, diff --git a/packages/common/src/events/_internal/PropertyMapper.ts b/packages/common/src/events/_internal/PropertyMapper.ts index aa2d81ae5af..d7be6d1a6bc 100644 --- a/packages/common/src/events/_internal/PropertyMapper.ts +++ b/packages/common/src/events/_internal/PropertyMapper.ts @@ -83,6 +83,12 @@ export class EventPropertyMapper extends PropertyMapper< store.permission.canSetStatusToCancelled, store.permission.canSetStatusToRemoved, ].some(Boolean); + obj.referencedContentIds = store.associations.map( + ({ entityId }) => entityId + ); + obj.referencedContentIdsByType = store.associations.map( + ({ entityId, entityType }) => ({ entityId, entityType }) + ); // Handle Dates obj.createdDate = new Date(store.createdAt); diff --git a/packages/common/src/events/_internal/buildEventAssociations.ts b/packages/common/src/events/_internal/buildEventAssociations.ts new file mode 100644 index 00000000000..3287633b62a --- /dev/null +++ b/packages/common/src/events/_internal/buildEventAssociations.ts @@ -0,0 +1,34 @@ +import { IHubRequestOptions } from "../../types"; +import { ICreateEventAssociation } from "../api"; +import { searchItems } from "@esri/arcgis-rest-portal"; + +export async function buildEventAssociations( + referencedContentIdsByType: Array<{ entityId: string; entityType: string }>, + referencedContentIds: string[], + hubRequestOptions: IHubRequestOptions +): Promise { + // filter out content that was removed + const associations = referencedContentIdsByType.filter(({ entityId }) => + referencedContentIds.includes(entityId) + ); + // get content ids being added + const added = referencedContentIds.filter( + (referencedContentId) => + !associations.find(({ entityId }) => entityId === referencedContentId) + ); + if (added.length) { + // fetch the content being added + const { results } = await searchItems({ + q: added.map((id) => `id:${id}`).join(" OR "), + num: added.length, + authentication: hubRequestOptions.authentication, + }); + // map content to ICreateEventAssociation structures + const addedAssociations = results.map(({ id, type }) => ({ + entityId: id, + entityType: type, + })); + associations.push(...addedAssociations); + } + return associations as ICreateEventAssociation[]; +} diff --git a/packages/common/src/events/_internal/buildReferencedContentSchema.ts b/packages/common/src/events/_internal/buildReferencedContentSchema.ts new file mode 100644 index 00000000000..a09ab5fb342 --- /dev/null +++ b/packages/common/src/events/_internal/buildReferencedContentSchema.ts @@ -0,0 +1,72 @@ +import { IArcGISContext } from "../../ArcGISContext"; +import { IUiSchemaElement } from "../../core/schemas/types"; +import { getWellKnownCatalog } from "../../search/wellKnownCatalog"; + +export function buildReferencedContentSchema( + i18nScope: string, + context: IArcGISContext, + label?: string +): IUiSchemaElement { + return { + scope: "/properties/referencedContentIds", + type: "Control", + label, + options: { + control: "hub-field-input-gallery-picker", + helperText: { + labelKey: `${i18nScope}.fields.referencedContent.helperText.label`, + }, + targetEntity: "item", + catalogs: [ + getWellKnownCatalog( + `${i18nScope}.fields.referencedContent`, + "organization", + "item", + { + user: context.currentUser, + collectionNames: ["site", "initiative", "project"], + filters: [], + context, + } + ), + ], + facets: [ + { + label: `{{${i18nScope}.fields.referencedContent.facets.from.label:translate}}`, + key: "from", + display: "single-select", + operation: "OR", + options: [ + { + label: `{{${i18nScope}.fields.referencedContent.facets.from.myContent.label:translate}}`, + key: "myContent", + selected: true, + predicates: [ + { + owner: context.currentUser.username, + }, + ], + }, + { + label: `{{${i18nScope}.fields.referencedContent.facets.from.myOrganization.label:translate}}`, + key: "myOrganization", + selected: false, + predicates: [ + { + orgId: context.currentUser.orgId, + }, + ], + }, + ], + }, + { + label: `{{${i18nScope}.fields.referencedContent.facets.access.label:translate}}`, + key: "access", + field: "access", + display: "multi-select", + operation: "OR", + }, + ], + }, + }; +} diff --git a/packages/common/src/events/defaults.ts b/packages/common/src/events/defaults.ts index 166d568a968..d0379ebf38d 100644 --- a/packages/common/src/events/defaults.ts +++ b/packages/common/src/events/defaults.ts @@ -31,7 +31,8 @@ export function buildDefaultEventEntity(): Partial { onlineCapacityType: HubEventCapacityType.Unlimited, onlineDetails: null, onlineUrl: null, - references: [], + referencedContentIds: [], + referencedContentIdsByType: [], schemaVersion: 1, tags: [], readGroupIds: [], @@ -57,6 +58,7 @@ export function buildDefaultEventRecord(): Partial { access: EventAccess.PRIVATE, allDay: false, allowRegistration: true, + associations: [], attendanceType: [EventAttendanceType.IN_PERSON], categories: [], inPersonCapacity: null, diff --git a/packages/common/src/events/edit.ts b/packages/common/src/events/edit.ts index 27964645bef..518dce1cc1c 100644 --- a/packages/common/src/events/edit.ts +++ b/packages/common/src/events/edit.ts @@ -15,6 +15,7 @@ import { IRegistration, RegistrationRole, } from "./api"; +import { buildEventAssociations } from "./_internal/buildEventAssociations"; export interface IHubCreateEventRegistration { eventId: string; @@ -30,7 +31,7 @@ export interface IHubCreateEventRegistration { * * @param partialEvent a partial event * @param requestOptions user request options - * @returns promise that resolves a IHubEvent + * @returns promise that resolves an IHubEvent */ export async function createHubEvent( partialEvent: Partial, @@ -48,10 +49,17 @@ export async function createHubEvent( let model = mapper.entityToStore(event, buildDefaultEventRecord()); + const associations = await buildEventAssociations( + partialEvent.referencedContentIdsByType, + partialEvent.referencedContentIds, + requestOptions + ); + const data = { access: model.access, allDay: model.allDay, allowRegistration: model.allowRegistration, + associations, attendanceType: model.attendanceType, categories: model.categories, description: model.description, @@ -98,10 +106,17 @@ export async function updateHubEvent( let model = mapper.entityToStore(eventUpdates, buildDefaultEventRecord()); + const associations = await buildEventAssociations( + partialEvent.referencedContentIdsByType, + partialEvent.referencedContentIds, + requestOptions + ); + const data = { access: model.access, allDay: model.allDay, allowRegistration: model.allowRegistration, + associations, attendanceType: model.attendanceType, categories: model.categories, description: model.description, diff --git a/packages/common/src/events/fetch.ts b/packages/common/src/events/fetch.ts index 1fa7a83f929..4668b69cbc7 100644 --- a/packages/common/src/events/fetch.ts +++ b/packages/common/src/events/fetch.ts @@ -20,6 +20,9 @@ export function fetchEvent( const id = spl[spl.length - 1]; return getEvent({ eventId: id, + data: { + include: "associations", + }, ...requestOptions, }) .then((event) => convertClientEventToHubEvent(event, requestOptions)) diff --git a/packages/common/src/search/_internal/hubEventsHelpers/processFilters.ts b/packages/common/src/search/_internal/hubEventsHelpers/processFilters.ts index 2e8d9ec76a1..e03a44d4077 100644 --- a/packages/common/src/search/_internal/hubEventsHelpers/processFilters.ts +++ b/packages/common/src/search/_internal/hubEventsHelpers/processFilters.ts @@ -117,5 +117,21 @@ export async function processFilters( startDateRange[0].from ).toISOString(); } + const endDateRange = getPredicateValuesByKey>( + filters, + "endDateRange" + ); + if (endDateRange.length) { + // TODO: remove below ts-ignore once https://devtopia.esri.com/dc/hub/issues/11097 is resolved + // @ts-ignore + processedFilters.endDateTimeBefore = new Date( + endDateRange[0].to + ).toISOString(); + // TODO: remove below ts-ignore once https://devtopia.esri.com/dc/hub/issues/11097 is resolved + // @ts-ignore + processedFilters.endDateTimeAfter = new Date( + endDateRange[0].from + ).toISOString(); + } return processedFilters; } diff --git a/packages/common/src/search/_internal/hubEventsHelpers/processOptions.ts b/packages/common/src/search/_internal/hubEventsHelpers/processOptions.ts index 8c16a01bf40..2f26ef01c1d 100644 --- a/packages/common/src/search/_internal/hubEventsHelpers/processOptions.ts +++ b/packages/common/src/search/_internal/hubEventsHelpers/processOptions.ts @@ -26,6 +26,8 @@ export function processOptions( processedOptions.sortBy = EventSort.createdAt; } else if (options.sortField === "title") { processedOptions.sortBy = EventSort.title; + } else if (options.sortField === "startDate") { + processedOptions.sortBy = EventSort.startDateTime; } processedOptions.sortOrder = options.sortOrder === "desc" ? EventSortOrder.desc : EventSortOrder.asc; diff --git a/packages/common/src/search/_internal/hubSearchEvents.ts b/packages/common/src/search/_internal/hubSearchEvents.ts index b1c90f0d3ab..09a10dcead3 100644 --- a/packages/common/src/search/_internal/hubSearchEvents.ts +++ b/packages/common/src/search/_internal/hubSearchEvents.ts @@ -11,22 +11,28 @@ import { processFilters } from "./hubEventsHelpers/processFilters"; /** * Searches for events against the Events 3 API using the given `query` and `options`. * Currently supported filters include: - * access: 'public' | 'private' | 'org' | Array<'public' | 'org' | 'access'>; - * canEdit: boolean - * entityId: string | string[]; - * entityType: string | string[]; - * id: string | string[]; - * userId: string; - * term: string; - * categories: string | string[]; - * tags: string | string[]; - * group: string | string[]; - * readGroupId: string | string[]; - * editGroupId: string | string[]; - * attendanceType: 'virtual' | 'in_person' | Array<'virtual' | 'in_person'>; - * owner: string | string[]; - * status: 'planned' | 'canceled' | 'removed' | Array<'planned' | 'canceled' | 'removed'>; - * startDateRange: IDateRange + * - access: 'public' | 'private' | 'org' | Array<'public' | 'org' | 'access'>; + * - canEdit: boolean + * - entityId: string | string[]; + * - entityType: string | string[]; + * - id: string | string[]; + * - userId: string; + * - term: string; + * - categories: string | string[]; + * - tags: string | string[]; + * - group: string | string[]; + * - readGroupId: string | string[]; + * - editGroupId: string | string[]; + * - attendanceType: 'virtual' | 'in_person' | Array<'virtual' | 'in_person'>; + * - owner: string | string[]; + * - status: 'planned' | 'canceled' | 'removed' | Array<'planned' | 'canceled' | 'removed'>; + * - startDateRange: IDateRange; + * - endDateRange: IDateRange; + * Currently supported sort fields include: + * - created + * - modified + * - title + * - startDate * @param query An IQuery object * @param options An IHubSearchOptions object * @returns a promise that resolves a object @@ -43,7 +49,7 @@ export async function hubSearchEvents( const data: GetEventsParams = { ...processedFilters, ...processedOptions, - include: "creator,registrations", + include: "creator", }; const { items, nextStart, total } = await getEvents({ ...options.requestOptions, diff --git a/packages/common/test/events/_internal/EventSchemaCreate.test.ts b/packages/common/test/events/_internal/EventSchemaCreate.test.ts index e2dec359e73..a5f4e80b4dc 100644 --- a/packages/common/test/events/_internal/EventSchemaCreate.test.ts +++ b/packages/common/test/events/_internal/EventSchemaCreate.test.ts @@ -57,6 +57,14 @@ describe("EventSchemaCreate", () => { onlineUrl: { type: "string", }, + referencedContentIds: { + type: "array", + maxItems: 1, + items: { + type: "string", + }, + default: [], + }, }, allOf: [ URL_VALIDATIONS_WHEN_ONLINE_OR_HYBRID, diff --git a/packages/common/test/events/_internal/EventSchemaEdit.test.ts b/packages/common/test/events/_internal/EventSchemaEdit.test.ts index 46eaecb1f2f..3878cbc0757 100644 --- a/packages/common/test/events/_internal/EventSchemaEdit.test.ts +++ b/packages/common/test/events/_internal/EventSchemaEdit.test.ts @@ -1,8 +1,8 @@ import { buildSchema } from "../../../src/events/_internal/EventSchemaEdit"; import { IConfigurationSchema } from "../../../src/core/schemas/types"; -import * as getDefaultEventDatesAndTimesModule from "../../../src/events/_internal/getDefaultEventDatesAndTimes"; import { ENTITY_CATEGORIES_SCHEMA, + ENTITY_FEATURED_CONTENT_SCHEMA, ENTITY_NAME_SCHEMA, ENTITY_SUMMARY_SCHEMA, ENTITY_TAGS_SCHEMA, @@ -30,12 +30,7 @@ describe("EventSchemaEdit", () => { endTime: "14:00:00", timeZone: "America/New_York", }; - const getDefaultEventDatesAndTimesSpy = spyOn( - getDefaultEventDatesAndTimesModule, - "getDefaultEventDatesAndTimes" - ).and.returnValue(datesAndTimes); const res = buildSchema(); - expect(getDefaultEventDatesAndTimesSpy).toHaveBeenCalledTimes(1); expect(res).toEqual({ required: ["name", "startDate", "endDate", "timeZone"], properties: { @@ -104,6 +99,14 @@ describe("EventSchemaEdit", () => { summary: ENTITY_SUMMARY_SCHEMA, tags: ENTITY_TAGS_SCHEMA, categories: ENTITY_CATEGORIES_SCHEMA, + referencedContentIds: { + type: "array", + maxItems: 1, + items: { + type: "string", + }, + default: [], + }, }, allOf: [ URL_VALIDATIONS_WHEN_ONLINE_OR_HYBRID, diff --git a/packages/common/test/events/_internal/EventUiSchemaCreate.test.ts b/packages/common/test/events/_internal/EventUiSchemaCreate.test.ts index 2f31e1e4b37..5cedd4aff00 100644 --- a/packages/common/test/events/_internal/EventUiSchemaCreate.test.ts +++ b/packages/common/test/events/_internal/EventUiSchemaCreate.test.ts @@ -6,8 +6,23 @@ import { MOCK_AUTH } from "../../mocks/mock-auth"; import { HubEventAttendanceType } from "../../../src/events/types"; import { UiSchemaRuleEffects } from "../../../src/core/schemas/types"; import * as getDatePickerDateUtils from "../../../src/utils/date/getDatePickerDate"; +import * as buildReferencedContentSchemaModule from "../../../src/events/_internal/buildReferencedContentSchema"; describe("EventUiSchemaCreate", () => { + const referencedContentSchema = { + scope: "/properties/referencedContentIds", + type: "Control", + }; + + let buildReferencedContentSchemaSpy: jasmine.Spy; + + beforeEach(() => { + buildReferencedContentSchemaSpy = spyOn( + buildReferencedContentSchemaModule, + "buildReferencedContentSchema" + ).and.returnValue(referencedContentSchema); + }); + describe("buildUiSchema", () => { it("should return the expected ui schema", async () => { const authdCtxMgr = await ArcGISContextManager.create({ @@ -67,6 +82,12 @@ describe("EventUiSchemaCreate", () => { jasmine.any(Date), entity.timeZone ); + expect(buildReferencedContentSchemaSpy).toHaveBeenCalledTimes(1); + expect(buildReferencedContentSchemaSpy).toHaveBeenCalledWith( + "myI18nScope", + authdCtxMgr.context, + "{{myI18nScope.fields.referencedContent.label:translate}}" + ); expect(res).toEqual({ type: "Layout", elements: [ @@ -217,6 +238,7 @@ describe("EventUiSchemaCreate", () => { ], }, }, + referencedContentSchema, ], }); }); diff --git a/packages/common/test/events/_internal/EventUiSchemaEdit.test.ts b/packages/common/test/events/_internal/EventUiSchemaEdit.test.ts index e7c985e8887..218ca7f39ce 100644 --- a/packages/common/test/events/_internal/EventUiSchemaEdit.test.ts +++ b/packages/common/test/events/_internal/EventUiSchemaEdit.test.ts @@ -8,34 +8,51 @@ import { HubEventCapacityType, } from "../../../src/events/types"; import { UiSchemaRuleEffects } from "../../../src/core/schemas/types"; -import * as getDatePickerDateUtils from "../../../src/utils/date/getDatePickerDate"; import * as fetchCategoryItemsModule from "../../../src/core/schemas/internal/fetchCategoryItems"; import * as getTagItemsModule from "../../../src/core/schemas/internal/getTagItems"; import * as getLocationExtentModule from "../../../src/core/schemas/internal/getLocationExtent"; import * as getLocationOptionsModule from "../../../src/core/schemas/internal/getLocationOptions"; +import * as buildReferencedContentSchemaModule from "../../../src/events/_internal/buildReferencedContentSchema"; describe("EventUiSchemaEdit", () => { + const referencedContentSchema = { + scope: "/properties/referencedContentIds", + type: "Control", + }; + + let buildReferencedContentSchemaSpy: jasmine.Spy; + beforeAll(() => { jasmine.clock().uninstall(); jasmine.clock().mockDate(new Date("2024-04-03T16:30:00.000Z")); }); + beforeEach(() => { + buildReferencedContentSchemaSpy = spyOn( + buildReferencedContentSchemaModule, + "buildReferencedContentSchema" + ).and.returnValue(referencedContentSchema); + }); + afterAll(() => { jasmine.clock().uninstall(); }); describe("buildUiSchema", () => { it("should return the expected ui schema", async () => { - spyOn(getLocationExtentModule, "getLocationExtent").and.returnValue( - Promise.resolve([]) - ); - spyOn(getLocationOptionsModule, "getLocationOptions").and.returnValue( - Promise.resolve([]) - ); + const getLocationExtentSpy = spyOn( + getLocationExtentModule, + "getLocationExtent" + ).and.returnValue(Promise.resolve([])); + const getLocationOptionsSpy = spyOn( + getLocationOptionsModule, + "getLocationOptions" + ).and.returnValue(Promise.resolve([])); const authdCtxMgr = await ArcGISContextManager.create({ authentication: MOCK_AUTH, currentUser: { username: "casey", + orgId: "9y2", } as unknown as PortalModule.IUser, portal: { name: "DC R&D Center", @@ -54,6 +71,8 @@ describe("EventUiSchemaEdit", () => { timeZone: "America/New_York", }; const entity = { + id: "t2c", + type: "Event", access: "private", allowRegistration: true, attendanceType: HubEventAttendanceType.InPerson, @@ -73,12 +92,9 @@ describe("EventUiSchemaEdit", () => { references: [], schemaVersion: 1, tags: ["tag1"], + location: { type: "none" }, ...datesAndTimes, } as unknown as IHubEvent; - const getDatePickerDateSpy = spyOn( - getDatePickerDateUtils, - "getDatePickerDate" - ).and.returnValue("2024-04-03"); const tags = [{ value: "tag1" }, { value: "tag2" }]; const getTagItemsSpy = spyOn( getTagItemsModule, @@ -94,10 +110,18 @@ describe("EventUiSchemaEdit", () => { entity, authdCtxMgr.context ); - expect(getDatePickerDateSpy).toHaveBeenCalledTimes(1); - expect(getDatePickerDateSpy).toHaveBeenCalledWith( - jasmine.any(Date), - entity.timeZone + expect(getLocationExtentSpy).toHaveBeenCalledTimes(1); + expect(getLocationExtentSpy).toHaveBeenCalledWith( + entity.location, + authdCtxMgr.context.hubRequestOptions + ); + expect(getLocationOptionsSpy).toHaveBeenCalledTimes(1); + expect(getLocationOptionsSpy).toHaveBeenCalledWith( + entity.id, + entity.type, + entity.location, + authdCtxMgr.context.portal.name, + authdCtxMgr.context.hubRequestOptions ); expect(getTagItemsSpy).toHaveBeenCalledTimes(1); expect(getTagItemsSpy).toHaveBeenCalledWith( @@ -110,15 +134,20 @@ describe("EventUiSchemaEdit", () => { authdCtxMgr.context.portal.id, authdCtxMgr.context.hubRequestOptions ); + expect(buildReferencedContentSchemaSpy).toHaveBeenCalledTimes(1); + expect(buildReferencedContentSchemaSpy).toHaveBeenCalledWith( + "myI18nScope", + authdCtxMgr.context + ); expect(res).toEqual({ type: "Layout", elements: [ { type: "Section", - labelKey: `myI18nScope.sections.eventInfo.label`, + labelKey: "myI18nScope.sections.eventInfo.label", elements: [ { - labelKey: `myI18nScope.fields.name.label`, + labelKey: "myI18nScope.fields.name.label", scope: "/properties/name", type: "Control", options: { @@ -127,26 +156,26 @@ describe("EventUiSchemaEdit", () => { type: "ERROR", keyword: "required", icon: true, - labelKey: `myI18nScope.fields.name.requiredError`, + labelKey: "myI18nScope.fields.name.requiredError", }, { type: "ERROR", keyword: "maxLength", icon: true, - labelKey: `shared.fields.title.maxLengthError`, + labelKey: "shared.fields.title.maxLengthError", }, ], }, }, { - labelKey: `myI18nScope.fields.description.label`, + labelKey: "myI18nScope.fields.description.label", scope: "/properties/description", type: "Control", options: { control: "hub-field-input-rich-text", type: "textarea", helperText: { - labelKey: `myI18nScope.fields.description.helperText`, + labelKey: "myI18nScope.fields.description.helperText", }, }, }, @@ -154,10 +183,10 @@ describe("EventUiSchemaEdit", () => { }, { type: "Section", - labelKey: `myI18nScope.sections.dateTime.label`, + labelKey: "myI18nScope.sections.dateTime.label", elements: [ { - labelKey: `myI18nScope.fields.startDate.label`, + labelKey: "myI18nScope.fields.startDate.label", scope: "/properties/startDate", type: "Control", options: { @@ -167,13 +196,13 @@ describe("EventUiSchemaEdit", () => { type: "ERROR", keyword: "required", icon: true, - labelKey: `myI18nScope.fields.startDate.requiredError`, + labelKey: "myI18nScope.fields.startDate.requiredError", }, ], }, }, { - labelKey: `myI18nScope.fields.endDate.label`, + labelKey: "myI18nScope.fields.endDate.label", scope: "/properties/endDate", type: "Control", options: { @@ -183,19 +212,19 @@ describe("EventUiSchemaEdit", () => { type: "ERROR", keyword: "required", icon: true, - labelKey: `myI18nScope.fields.endDate.requiredError`, + labelKey: "myI18nScope.fields.endDate.requiredError", }, { type: "ERROR", keyword: "formatMinimum", icon: true, - labelKey: `myI18nScope.fields.endDate.minDateError`, + labelKey: "myI18nScope.fields.endDate.minDateError", }, ], }, }, { - labelKey: `myI18nScope.fields.allDay.label`, + labelKey: "myI18nScope.fields.allDay.label", type: "Control", scope: "/properties/isAllDay", options: { @@ -203,7 +232,7 @@ describe("EventUiSchemaEdit", () => { }, }, { - labelKey: `myI18nScope.fields.startTime.label`, + labelKey: "myI18nScope.fields.startTime.label", scope: "/properties/startTime", type: "Control", rule: { @@ -220,13 +249,13 @@ describe("EventUiSchemaEdit", () => { type: "ERROR", keyword: "required", icon: true, - labelKey: `myI18nScope.fields.startTime.requiredError`, + labelKey: "myI18nScope.fields.startTime.requiredError", }, ], }, }, { - labelKey: `myI18nScope.fields.endTime.label`, + labelKey: "myI18nScope.fields.endTime.label", scope: "/properties/endTime", type: "Control", rule: { @@ -243,19 +272,19 @@ describe("EventUiSchemaEdit", () => { type: "ERROR", keyword: "required", icon: true, - labelKey: `myI18nScope.fields.endTime.requiredError`, + labelKey: "myI18nScope.fields.endTime.requiredError", }, { type: "ERROR", keyword: "formatExclusiveMinimum", icon: true, - labelKey: `myI18nScope.fields.endTime.minTimeError`, + labelKey: "myI18nScope.fields.endTime.minTimeError", }, ], }, }, { - labelKey: `myI18nScope.fields.timeZone.label`, + labelKey: "myI18nScope.fields.timeZone.label", scope: "/properties/timeZone", type: "Control", options: { @@ -265,7 +294,7 @@ describe("EventUiSchemaEdit", () => { type: "ERROR", keyword: "required", icon: true, - labelKey: `myI18nScope.fields.timeZone.requiredError`, + labelKey: "myI18nScope.fields.timeZone.requiredError", }, ], }, @@ -274,21 +303,21 @@ describe("EventUiSchemaEdit", () => { }, { type: "Section", - labelKey: `myI18nScope.sections.location.label`, + labelKey: "myI18nScope.sections.location.label", elements: [ { - labelKey: `myI18nScope.fields.attendanceType.label`, + labelKey: "myI18nScope.fields.attendanceType.label", scope: "/properties/attendanceType", type: "Control", options: { control: "hub-field-input-radio-group", - enum: { i18nScope: `myI18nScope.fields.attendanceType` }, + enum: { i18nScope: "myI18nScope.fields.attendanceType" }, }, }, { scope: "/properties/location", type: "Control", - labelKey: `myI18nScope.fields.location.label`, + labelKey: "myI18nScope.fields.location.label", options: { control: "hub-field-input-location-picker", extent: [], @@ -296,7 +325,7 @@ describe("EventUiSchemaEdit", () => { }, }, { - labelKey: `myI18nScope.fields.inPersonCapacityType.label`, + labelKey: "myI18nScope.fields.inPersonCapacityType.label", scope: "/properties/inPersonCapacityType", type: "Control", rule: { @@ -314,12 +343,12 @@ describe("EventUiSchemaEdit", () => { options: { control: "hub-field-input-radio-group", enum: { - i18nScope: `myI18nScope.fields.inPersonCapacityType`, + i18nScope: "myI18nScope.fields.inPersonCapacityType", }, }, }, { - labelKey: `myI18nScope.fields.inPersonCapacity.label`, + labelKey: "myI18nScope.fields.inPersonCapacity.label", scope: "/properties/inPersonCapacity", type: "Control", rule: { @@ -348,19 +377,21 @@ describe("EventUiSchemaEdit", () => { type: "ERROR", keyword: "required", icon: true, - labelKey: `myI18nScope.fields.inPersonCapacity.requiredError`, + labelKey: + "myI18nScope.fields.inPersonCapacity.requiredError", }, { type: "ERROR", keyword: "minimum", icon: true, - labelKey: `myI18nScope.fields.inPersonCapacity.minimumError`, + labelKey: + "myI18nScope.fields.inPersonCapacity.minimumError", }, ], }, }, { - labelKey: `myI18nScope.fields.onlineUrl.label`, + labelKey: "myI18nScope.fields.onlineUrl.label", scope: "/properties/onlineUrl", type: "Control", rule: { @@ -382,19 +413,19 @@ describe("EventUiSchemaEdit", () => { type: "ERROR", keyword: "required", icon: true, - labelKey: `myI18nScope.fields.onlineUrl.requiredError`, + labelKey: "myI18nScope.fields.onlineUrl.requiredError", }, { type: "ERROR", keyword: "format", icon: true, - labelKey: `shared.errors.urlFormat`, + labelKey: "shared.errors.urlFormat", }, ], }, }, { - labelKey: `myI18nScope.fields.onlineDetails.label`, + labelKey: "myI18nScope.fields.onlineDetails.label", scope: "/properties/onlineDetails", type: "Control", rule: { @@ -413,12 +444,12 @@ describe("EventUiSchemaEdit", () => { control: "hub-field-input-rich-text", type: "textarea", helperText: { - labelKey: `myI18nScope.fields.onlineDetails.helperText`, + labelKey: "myI18nScope.fields.onlineDetails.helperText", }, }, }, { - labelKey: `myI18nScope.fields.onlineCapacityType.label`, + labelKey: "myI18nScope.fields.onlineCapacityType.label", scope: "/properties/onlineCapacityType", type: "Control", rule: { @@ -435,11 +466,11 @@ describe("EventUiSchemaEdit", () => { }, options: { control: "hub-field-input-radio-group", - enum: { i18nScope: `myI18nScope.fields.onlineCapacityType` }, + enum: { i18nScope: "myI18nScope.fields.onlineCapacityType" }, }, }, { - labelKey: `myI18nScope.fields.onlineCapacity.label`, + labelKey: "myI18nScope.fields.onlineCapacity.label", scope: "/properties/onlineCapacity", type: "Control", rule: { @@ -468,13 +499,15 @@ describe("EventUiSchemaEdit", () => { type: "ERROR", keyword: "required", icon: true, - labelKey: `myI18nScope.fields.onlineCapacity.requiredError`, + labelKey: + "myI18nScope.fields.onlineCapacity.requiredError", }, { type: "ERROR", keyword: "minimum", icon: true, - labelKey: `myI18nScope.fields.onlineCapacity.minimumError`, + labelKey: + "myI18nScope.fields.onlineCapacity.minimumError", }, ], }, @@ -483,10 +516,15 @@ describe("EventUiSchemaEdit", () => { }, { type: "Section", - labelKey: `myI18nScope.sections.discoverability.label`, + labelKey: `myI18nScope.sections.referencedContent.label`, + elements: [referencedContentSchema], + }, + { + type: "Section", + labelKey: "myI18nScope.sections.discoverability.label", elements: [ { - labelKey: `myI18nScope.fields.tags.label`, + labelKey: "myI18nScope.fields.tags.label", scope: "/properties/tags", type: "Control", options: { @@ -496,7 +534,7 @@ describe("EventUiSchemaEdit", () => { selectionMode: "multiple", placeholderIcon: "label", helperText: { - labelKey: `myI18nScope.fields.tags.helperText`, + labelKey: "myI18nScope.fields.tags.helperText", }, }, }, @@ -555,7 +593,7 @@ describe("EventUiSchemaEdit", () => { ], }, { - labelKey: `myI18nScope.fields.summary.label`, + labelKey: "myI18nScope.fields.summary.label", scope: "/properties/summary", type: "Control", options: { @@ -563,14 +601,14 @@ describe("EventUiSchemaEdit", () => { type: "textarea", rows: 4, helperText: { - labelKey: `myI18nScope.fields.summary.helperText`, + labelKey: "myI18nScope.fields.summary.helperText", }, messages: [ { type: "ERROR", keyword: "maxLength", icon: true, - labelKey: `shared.fields.summary.maxLengthError`, + labelKey: "shared.fields.summary.maxLengthError", }, ], }, diff --git a/packages/common/test/events/_internal/PropertyMapper.test.ts b/packages/common/test/events/_internal/PropertyMapper.test.ts index de76dc10979..c4fe800e86c 100644 --- a/packages/common/test/events/_internal/PropertyMapper.test.ts +++ b/packages/common/test/events/_internal/PropertyMapper.test.ts @@ -95,6 +95,13 @@ describe("PropertyMapper", () => { timeZone: "America/New_York", title: "event title", updatedAt: now.toISOString(), + associations: [ + { + eventId: "31c", + entityId: "9v2", + entityType: "Hub Site Application", + }, + ], } as IEvent; }); @@ -173,6 +180,13 @@ describe("PropertyMapper", () => { view: { showMap: true, }, + referencedContentIds: ["9v2"], + referencedContentIdsByType: [ + { + entityId: "9v2", + entityType: "Hub Site Application", + }, + ], }); }); diff --git a/packages/common/test/events/_internal/buildEventAssociations.test.ts b/packages/common/test/events/_internal/buildEventAssociations.test.ts new file mode 100644 index 00000000000..2250edb6e16 --- /dev/null +++ b/packages/common/test/events/_internal/buildEventAssociations.test.ts @@ -0,0 +1,120 @@ +import * as restPortalModule from "@esri/arcgis-rest-portal"; +import { buildEventAssociations } from "../../../src/events/_internal/buildEventAssociations"; +import { IHubRequestOptions } from "../../../src/types"; +import { ICreateEventAssociation } from "../../../src/events/api/orval/api/orval-events"; + +describe("buildEventAssociations", () => { + const hubRequestOptions = { authentication: {} } as IHubRequestOptions; + let searchItemsSpy: jasmine.Spy; + + beforeEach(() => { + searchItemsSpy = spyOn(restPortalModule, "searchItems").and.returnValue( + Promise.resolve({ + results: [ + { + id: "31c", + type: "Hub Site Application", + }, + { + id: "53e", + type: "Hub Initiative", + }, + ], + }) + ); + }); + + it("should purge associations not included in featuredContentIds", async () => { + const existingAssociations = [ + { + entityId: "31c", + entityType: "Hub Site Application", + }, + { + entityId: "42d", + entityType: "Hub Project", + }, + { + entityId: "53e", + entityType: "Hub Initiative", + }, + ]; + const updatedFeaturedContentIds = ["31c", "53e"]; + const results = await buildEventAssociations( + existingAssociations, + updatedFeaturedContentIds, + hubRequestOptions + ); + expect(searchItemsSpy).not.toHaveBeenCalled(); + expect(results).toEqual([ + { + entityId: "31c", + entityType: "Hub Site Application", + }, + { + entityId: "53e", + entityType: "Hub Initiative", + }, + ] as ICreateEventAssociation[]); + }); + + it("should resolve the existing associations when none are added or removed", async () => { + const existingAssociations = [ + { + entityId: "31c", + entityType: "Hub Site Application", + }, + { + entityId: "42d", + entityType: "Hub Project", + }, + { + entityId: "53e", + entityType: "Hub Initiative", + }, + ]; + const updatedFeaturedContentIds = ["31c", "42d", "53e"]; + const results = await buildEventAssociations( + existingAssociations, + updatedFeaturedContentIds, + hubRequestOptions + ); + expect(searchItemsSpy).not.toHaveBeenCalled(); + expect(results).toEqual(existingAssociations as ICreateEventAssociation[]); + }); + + it("should fetch and add new associations", async () => { + const existingAssociations = [ + { + entityId: "42d", + entityType: "Hub Project", + }, + ]; + const updatedFeaturedContentIds = ["31c", "42d", "53e"]; + const results = await buildEventAssociations( + existingAssociations, + updatedFeaturedContentIds, + hubRequestOptions + ); + expect(searchItemsSpy).toHaveBeenCalledTimes(1); + expect(searchItemsSpy).toHaveBeenCalledWith({ + q: "id:31c OR id:53e", + num: 2, + authentication: hubRequestOptions.authentication, + }); + expect(results).toEqual([ + { + entityId: "42d", + entityType: "Hub Project", + }, + { + entityId: "31c", + entityType: "Hub Site Application", + }, + { + entityId: "53e", + entityType: "Hub Initiative", + }, + ] as ICreateEventAssociation[]); + }); +}); diff --git a/packages/common/test/events/_internal/buildReferencedContentSchema.test.ts b/packages/common/test/events/_internal/buildReferencedContentSchema.test.ts new file mode 100644 index 00000000000..790a0cee58f --- /dev/null +++ b/packages/common/test/events/_internal/buildReferencedContentSchema.test.ts @@ -0,0 +1,152 @@ +import { IArcGISContext } from "../../../src/ArcGISContext"; +import { IUiSchemaElement } from "../../../src/core/schemas/types"; +import { buildReferencedContentSchema } from "../../../src/events/_internal/buildReferencedContentSchema"; +import * as wellKnownCatalogModule from "../../../src/search/wellKnownCatalog"; + +describe("buildReferencedContentSchema", () => { + const catalog = { catalog: true }; + const i18nScope = "myI18nScope"; + const context = { + currentUser: { + username: "user1", + orgId: "org1", + }, + } as unknown as IArcGISContext; + + let getWellKnownCatalogSpy: jasmine.Spy; + + beforeEach(() => { + getWellKnownCatalogSpy = spyOn( + wellKnownCatalogModule, + "getWellKnownCatalog" + ).and.returnValue(catalog); + }); + + it("should return the schema element for referenced content without a label", () => { + const result = buildReferencedContentSchema(i18nScope, context); + expect(getWellKnownCatalogSpy).toHaveBeenCalledTimes(1); + expect(getWellKnownCatalogSpy).toHaveBeenCalledWith( + `${i18nScope}.fields.referencedContent`, + "organization", + "item", + { + user: context.currentUser, + collectionNames: ["site", "initiative", "project"], + filters: [], + context, + } + ); + expect(result).toEqual({ + scope: "/properties/referencedContentIds", + type: "Control", + label: undefined, + options: { + control: "hub-field-input-gallery-picker", + helperText: { + labelKey: "myI18nScope.fields.referencedContent.helperText.label", + }, + targetEntity: "item", + catalogs: [{ catalog: true }], + facets: [ + { + label: + "{{myI18nScope.fields.referencedContent.facets.from.label:translate}}", + key: "from", + display: "single-select", + operation: "OR", + options: [ + { + label: + "{{myI18nScope.fields.referencedContent.facets.from.myContent.label:translate}}", + key: "myContent", + selected: true, + predicates: [{ owner: "user1" }], + }, + { + label: + "{{myI18nScope.fields.referencedContent.facets.from.myOrganization.label:translate}}", + key: "myOrganization", + selected: false, + predicates: [{ orgId: "org1" }], + }, + ], + }, + { + label: + "{{myI18nScope.fields.referencedContent.facets.access.label:translate}}", + key: "access", + field: "access", + display: "multi-select", + operation: "OR", + }, + ], + }, + }); + }); + + it("should return the schema element for referenced content with the provided label", () => { + const result = buildReferencedContentSchema( + i18nScope, + context, + "some.label" + ); + expect(getWellKnownCatalogSpy).toHaveBeenCalledTimes(1); + expect(getWellKnownCatalogSpy).toHaveBeenCalledWith( + `${i18nScope}.fields.referencedContent`, + "organization", + "item", + { + user: context.currentUser, + collectionNames: ["site", "initiative", "project"], + filters: [], + context, + } + ); + expect(result).toEqual({ + scope: "/properties/referencedContentIds", + type: "Control", + label: "some.label", + options: { + control: "hub-field-input-gallery-picker", + helperText: { + labelKey: "myI18nScope.fields.referencedContent.helperText.label", + }, + targetEntity: "item", + catalogs: [{ catalog: true }], + facets: [ + { + label: + "{{myI18nScope.fields.referencedContent.facets.from.label:translate}}", + key: "from", + display: "single-select", + operation: "OR", + options: [ + { + label: + "{{myI18nScope.fields.referencedContent.facets.from.myContent.label:translate}}", + key: "myContent", + selected: true, + predicates: [{ owner: "user1" }], + }, + { + label: + "{{myI18nScope.fields.referencedContent.facets.from.myOrganization.label:translate}}", + key: "myOrganization", + selected: false, + predicates: [{ orgId: "org1" }], + }, + ], + }, + { + label: + "{{myI18nScope.fields.referencedContent.facets.access.label:translate}}", + key: "access", + field: "access", + display: "multi-select", + operation: "OR", + }, + ], + }, + }); + }); +}); diff --git a/packages/common/test/events/defaults.test.ts b/packages/common/test/events/defaults.test.ts index d12de748094..0feaf45da58 100644 --- a/packages/common/test/events/defaults.test.ts +++ b/packages/common/test/events/defaults.test.ts @@ -2,7 +2,6 @@ import * as getDefaultEventDatesAndTimesModule from "../../src/events/_internal/ import { EventAccess, EventAttendanceType, - EventLocationType, EventStatus, IEventLocation, } from "../../src/events/api/types"; @@ -49,7 +48,6 @@ describe("HubEvent defaults:", () => { onlineCapacityType: HubEventCapacityType.Unlimited, onlineDetails: null, onlineUrl: null, - references: [], schemaVersion: 1, tags: [], readGroupIds: [], @@ -58,6 +56,8 @@ describe("HubEvent defaults:", () => { heroActions: [], showMap: false, }, + referencedContentIds: [], + referencedContentIdsByType: [], location: { type: "none", }, @@ -98,6 +98,7 @@ describe("HubEvent defaults:", () => { tags: [], title: "", location: null as unknown as IEventLocation, + associations: [], }); expect(getDefaultEventDatesAndTimesSpy).toHaveBeenCalledTimes(1); }); diff --git a/packages/common/test/events/edit.test.ts b/packages/common/test/events/edit.test.ts index 7994c7f880c..685bafe957f 100644 --- a/packages/common/test/events/edit.test.ts +++ b/packages/common/test/events/edit.test.ts @@ -1,5 +1,4 @@ import * as PortalModule from "@esri/arcgis-rest-portal"; -import { ArcGISContextManager } from "../../src/ArcGISContextManager"; import { MOCK_AUTH } from "../mocks/mock-auth"; import * as defaultsModule from "../../src/events/defaults"; import * as eventsModule from "../../src/events/api/events"; @@ -9,6 +8,7 @@ import { EventAttendanceType, EventStatus, IEvent, + IEventAssociation, } from "../../src/events/api/types"; import { createHubEvent, @@ -23,112 +23,157 @@ import { HubEventAttendanceType, HubEventCapacityType, } from "../../src/events/types"; +import * as buildEventAssociationsModule from "../../src/events/_internal/buildEventAssociations"; +import { IArcGISContext } from "../../src/ArcGISContext"; describe("HubEvents edit module", () => { - describe("createHubEvent", () => { - it("should create an event", async () => { - const authdCtxMgr = await ArcGISContextManager.create({ - authentication: MOCK_AUTH, - currentUser: { - username: "casey", - } as unknown as PortalModule.IUser, - portal: { - name: "DC R&D Center", - id: "BRXFAKE", - urlKey: "fake-org", - } as unknown as PortalModule.IPortal, - portalUrl: "https://myserver.com", - }); - const datesAndTimes = { - startDate: "2024-03-31", - startDateTime: new Date(), - startTime: "12:00:00", - endDate: "2024-03-31", - endDateTime: new Date(), - endTime: "14:00:00", - timeZone: "America/New_York", - }; - const defaultEntity: Partial = { - access: "private", - allowRegistration: true, - attendanceType: HubEventAttendanceType.InPerson, - categories: [], - inPersonCapacity: null, - inPersonCapacityType: HubEventCapacityType.Unlimited, - isAllDay: false, - isCanceled: false, - isDiscussable: true, - isPlanned: true, - isRemoved: false, - name: "", - notifyAttendees: true, - onlineCapacity: null, - onlineCapacityType: HubEventCapacityType.Unlimited, - onlineDetails: null, - onlineUrl: null, - references: [], - schemaVersion: 1, - tags: [], - readGroupIds: [], - editGroupIds: [], - view: { - heroActions: [], - showMap: false, - }, - location: { - type: "none", - }, - ...datesAndTimes, - }; - const defaultRecord: Partial = { - access: EventAccess.PRIVATE, - allDay: false, - allowRegistration: true, - attendanceType: [EventAttendanceType.IN_PERSON], - categories: [], - editGroups: [], - endDateTime: datesAndTimes.endDateTime.toISOString(), - endDate: datesAndTimes.endDate, - endTime: datesAndTimes.endTime, - inPersonCapacity: 50, - notifyAttendees: true, - readGroups: [], - registrationCount: { - inPerson: 0, - virtual: 5, + const context = { + authentication: MOCK_AUTH, + currentUser: { + username: "casey", + } as unknown as PortalModule.IUser, + portal: { + name: "DC R&D Center", + id: "BRXFAKE", + urlKey: "fake-org", + } as unknown as PortalModule.IPortal, + portalUrl: "https://myserver.com", + } as unknown as IArcGISContext; + + const datesAndTimes = { + startDate: "2024-03-31", + startDateTime: new Date(), + startTime: "12:00:00", + endDate: "2024-03-31", + endDateTime: new Date(), + endTime: "14:00:00", + timeZone: "America/New_York", + }; + + const defaultRecord: Partial = { + access: EventAccess.PRIVATE, + allDay: false, + allowRegistration: true, + attendanceType: [EventAttendanceType.IN_PERSON], + associations: [], + categories: [], + editGroups: [], + endDateTime: datesAndTimes.endDateTime.toISOString(), + endDate: datesAndTimes.endDate, + endTime: datesAndTimes.endTime, + inPersonCapacity: 50, + notifyAttendees: true, + readGroups: [], + registrationCount: { + inPerson: 0, + virtual: 5, + }, + startDateTime: datesAndTimes.startDateTime.toISOString(), + startDate: datesAndTimes.startDate, + startTime: datesAndTimes.startTime, + status: EventStatus.PLANNED, + tags: [], + title: "", + permission: { + canDelete: true, + canSetAccessToOrg: true, + canSetAccessToPrivate: true, + canSetStatusToCancelled: true, + canEdit: true, + canSetAccessToPublic: true, + canSetStatusToRemoved: true, + }, + timeZone: "America/New_York", + }; + + const defaultEntity: Partial = { + access: "private", + allowRegistration: true, + attendanceType: HubEventAttendanceType.InPerson, + categories: [], + inPersonCapacity: null, + inPersonCapacityType: HubEventCapacityType.Unlimited, + isAllDay: false, + isCanceled: false, + isDiscussable: true, + isPlanned: true, + isRemoved: false, + name: "", + notifyAttendees: true, + onlineCapacity: null, + onlineCapacityType: HubEventCapacityType.Unlimited, + onlineDetails: null, + onlineUrl: null, + references: [], + schemaVersion: 1, + tags: [], + readGroupIds: [], + editGroupIds: [], + view: { + heroActions: [], + showMap: false, + }, + referencedContentIds: [], + referencedContentIdsByType: [], + location: { + type: "none", + }, + ...datesAndTimes, + }; + + let buildDefaultEventEntitySpy: jasmine.Spy; + let buildDefaultEventRecordSpy: jasmine.Spy; + let buildEventAssociationsSpy: jasmine.Spy; + + beforeEach(() => { + buildDefaultEventEntitySpy = spyOn( + defaultsModule, + "buildDefaultEventEntity" + ).and.returnValue(defaultEntity); + buildDefaultEventRecordSpy = spyOn( + defaultsModule, + "buildDefaultEventRecord" + ).and.returnValue(defaultRecord); + buildEventAssociationsSpy = spyOn( + buildEventAssociationsModule, + "buildEventAssociations" + ).and.returnValue( + Promise.resolve([ + { + entityId: "t36", + entityType: "Hub Site Application", }, - startDateTime: datesAndTimes.startDateTime.toISOString(), - startDate: datesAndTimes.startDate, - startTime: datesAndTimes.startTime, - status: EventStatus.PLANNED, - tags: [], - title: "", - permission: { - canDelete: true, - canSetAccessToOrg: true, - canSetAccessToPrivate: true, - canSetStatusToCancelled: true, - canEdit: true, - canSetAccessToPublic: true, - canSetStatusToRemoved: true, + { + entityId: "8nd", + entityType: "Hub Project", }, - timeZone: "America/New_York", - }; + ]) + ); + }); + + describe("createHubEvent", () => { + it("should create an event", async () => { const createdRecord = { ...defaultRecord, + id: "92x", title: "my event", timeZone: "America/New_York", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), + associations: [ + { + eventId: "92x", + entityId: "t36", + entityType: "Hub Site Application", + }, + { + eventId: "92x", + entityId: "8nd", + entityType: "Hub Project", + }, + ], }; - const buildDefaultEventEntitySpy = spyOn( - defaultsModule, - "buildDefaultEventEntity" - ).and.returnValue(defaultEntity); - const buildDefaultEventRecordSpy = spyOn( - defaultsModule, - "buildDefaultEventRecord" - ).and.returnValue(defaultRecord); + const createEventApiSpy = spyOn( eventsModule, "createEvent" @@ -145,11 +190,31 @@ describe("HubEvents edit module", () => { extent: [[]], geometries: [], }, + referencedContentIds: ["8nd"], + referencedContentIdsByType: [ + { + entityId: "t36", + entityType: "Hub Site Application", + }, + ], }, - authdCtxMgr.context.hubRequestOptions + context.hubRequestOptions ); expect(buildDefaultEventEntitySpy).toHaveBeenCalledTimes(1); + expect(buildDefaultEventEntitySpy).toHaveBeenCalledWith(); expect(buildDefaultEventRecordSpy).toHaveBeenCalledTimes(1); + expect(buildDefaultEventRecordSpy).toHaveBeenCalledWith(); + expect(buildEventAssociationsSpy).toHaveBeenCalledTimes(1); + expect(buildEventAssociationsSpy).toHaveBeenCalledWith( + [ + { + entityId: "t36", + entityType: "Hub Site Application", + }, + ], + ["8nd"], + context.hubRequestOptions + ); expect(createEventApiSpy).toHaveBeenCalledTimes(1); expect(createEventApiSpy).toHaveBeenCalledWith({ data: { @@ -157,17 +222,25 @@ describe("HubEvents edit module", () => { allDay: defaultRecord.allDay, allowRegistration: defaultRecord.allowRegistration, attendanceType: defaultRecord.attendanceType, + associations: [ + { + entityId: "t36", + entityType: "Hub Site Application", + }, + { + entityId: "8nd", + entityType: "Hub Project", + }, + ], categories: defaultRecord.categories, description: defaultRecord.description, editGroups: defaultRecord.editGroups, - // endDateTime not included endDate: defaultRecord.endDate, endTime: defaultRecord.endTime, inPersonCapacity: defaultRecord.inPersonCapacity, notifyAttendees: defaultRecord.notifyAttendees, onlineMeeting: defaultRecord.onlineMeeting, readGroups: defaultRecord.readGroups, - // startDateTime not included startDate: defaultRecord.startDate, startTime: defaultRecord.startTime, summary: defaultRecord.summary, @@ -181,7 +254,7 @@ describe("HubEvents edit module", () => { geometries: [], }, }, - ...authdCtxMgr.context.hubRequestOptions, + ...context.hubRequestOptions, }); expect(res.name).toEqual("my event"); }); @@ -189,108 +262,26 @@ describe("HubEvents edit module", () => { describe("updateHubEvent", () => { it("should update an event", async () => { - const authdCtxMgr = await ArcGISContextManager.create({ - authentication: MOCK_AUTH, - currentUser: { - username: "casey", - } as unknown as PortalModule.IUser, - portal: { - name: "DC R&D Center", - id: "BRXFAKE", - urlKey: "fake-org", - } as unknown as PortalModule.IPortal, - portalUrl: "https://myserver.com", - }); - const datesAndTimes = { - startDate: "2024-03-31", - startDateTime: new Date(), - startTime: "12:00:00", - endDate: "2024-03-31", - endDateTime: new Date(), - endTime: "14:00:00", - timeZone: "America/New_York", - }; - const defaultEntity: Partial = { - access: "private", - allowRegistration: true, - attendanceType: HubEventAttendanceType.InPerson, - categories: [], - inPersonCapacity: null, - inPersonCapacityType: HubEventCapacityType.Unlimited, - isAllDay: false, - isCanceled: false, - isDiscussable: true, - isPlanned: true, - isRemoved: false, - name: "", - notifyAttendees: true, - onlineCapacity: null, - onlineCapacityType: HubEventCapacityType.Unlimited, - onlineDetails: null, - onlineUrl: null, - references: [], - schemaVersion: 1, - tags: [], - readGroupIds: [], - editGroupIds: [], - view: { - heroActions: [], - showMap: false, - }, - location: { - type: "none", - }, - ...datesAndTimes, - }; - const defaultRecord: Partial = { - access: EventAccess.PRIVATE, - allDay: false, - allowRegistration: true, - attendanceType: [EventAttendanceType.IN_PERSON], - categories: [], - editGroups: [], - endDateTime: datesAndTimes.endDateTime.toISOString(), - endDate: datesAndTimes.endDate, - endTime: datesAndTimes.endTime, - inPersonCapacity: 50, - notifyAttendees: true, - readGroups: [], - registrationCount: { - inPerson: 0, - virtual: 5, - }, - startDateTime: datesAndTimes.startDateTime.toISOString(), - startDate: datesAndTimes.startDate, - startTime: datesAndTimes.startTime, - status: EventStatus.PLANNED, - tags: [], - title: "", - permission: { - canDelete: true, - canSetAccessToOrg: true, - canSetAccessToPrivate: true, - canSetStatusToCancelled: true, - canEdit: true, - canSetAccessToPublic: true, - canSetStatusToRemoved: true, - }, - timeZone: "America/New_York", - }; const updatedRecord = { ...defaultRecord, + id: "92x", title: "my event", timeZone: "America/New_York", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), + associations: [ + { + eventId: "92x", + entityId: "t36", + entityType: "Hub Site Application", + }, + { + eventId: "92x", + entityId: "8nd", + entityType: "Hub Project", + }, + ] as IEventAssociation[], }; - const buildDefaultEventEntitySpy = spyOn( - defaultsModule, - "buildDefaultEventEntity" - ).and.returnValue(defaultEntity); - const buildDefaultEventRecordSpy = spyOn( - defaultsModule, - "buildDefaultEventRecord" - ).and.returnValue(defaultRecord); const updateEventApiSpy = spyOn( eventsModule, "updateEvent" @@ -299,7 +290,7 @@ describe("HubEvents edit module", () => { { name: "my event", timeZone: "America/New_York", - id: "31c", + id: "92x", isCanceled: true, inPersonCapacity: 50, inPersonCapacityType: HubEventCapacityType.Fixed, @@ -309,30 +300,58 @@ describe("HubEvents edit module", () => { extent: [[]], geometries: [], }, + referencedContentIds: ["8nd"], + referencedContentIdsByType: [ + { + entityId: "t36", + entityType: "Hub Site Application", + }, + ], }, - authdCtxMgr.context.hubRequestOptions + context.hubRequestOptions ); expect(buildDefaultEventEntitySpy).toHaveBeenCalledTimes(1); + expect(buildDefaultEventEntitySpy).toHaveBeenCalledWith(); expect(buildDefaultEventRecordSpy).toHaveBeenCalledTimes(1); + expect(buildDefaultEventRecordSpy).toHaveBeenCalledWith(); + expect(buildEventAssociationsSpy).toHaveBeenCalledTimes(1); + expect(buildEventAssociationsSpy).toHaveBeenCalledWith( + [ + { + entityId: "t36", + entityType: "Hub Site Application", + }, + ], + ["8nd"], + context.hubRequestOptions + ); expect(updateEventApiSpy).toHaveBeenCalledTimes(1); expect(updateEventApiSpy).toHaveBeenCalledWith({ - eventId: "31c", + eventId: "92x", data: { access: defaultRecord.access, allDay: defaultRecord.allDay, allowRegistration: defaultRecord.allowRegistration, attendanceType: defaultRecord.attendanceType, + associations: [ + { + entityId: "t36", + entityType: "Hub Site Application", + }, + { + entityId: "8nd", + entityType: "Hub Project", + }, + ], categories: defaultRecord.categories, description: defaultRecord.description, editGroups: defaultRecord.editGroups, - // endDateTime not included endDate: defaultRecord.endDate, endTime: defaultRecord.endTime, inPersonCapacity: defaultRecord.inPersonCapacity, notifyAttendees: defaultRecord.notifyAttendees, onlineMeeting: defaultRecord.onlineMeeting, readGroups: defaultRecord.readGroups, - // startDateTime not included startDate: defaultRecord.startDate, startTime: defaultRecord.startTime, status: EventStatus.CANCELED, @@ -347,7 +366,7 @@ describe("HubEvents edit module", () => { geometries: [], }, }, - ...authdCtxMgr.context.hubRequestOptions, + ...context.hubRequestOptions, }); expect(res.name).toEqual("my event"); }); @@ -355,45 +374,21 @@ describe("HubEvents edit module", () => { describe("deleteHubEvent", () => { it("calls deleteEvent", async () => { - const authdCtxMgr = await ArcGISContextManager.create({ - authentication: MOCK_AUTH, - currentUser: { - username: "casey", - } as unknown as PortalModule.IUser, - portal: { - name: "DC R&D Center", - id: "BRXFAKE", - urlKey: "fake-org", - } as unknown as PortalModule.IPortal, - portalUrl: "https://myserver.com", - }); const deleteEventSpy = spyOn(eventsModule, "deleteEvent").and.callFake( () => { return Promise.resolve(); } ); - await deleteHubEvent("0o1", authdCtxMgr.context.hubRequestOptions); + await deleteHubEvent("0o1", context.hubRequestOptions); expect(deleteEventSpy).toHaveBeenCalledWith({ eventId: "0o1", - ...authdCtxMgr.context.hubRequestOptions, + ...context.hubRequestOptions, }); }); }); describe("createHubEventRegistration", () => { it("calls createRegistration", async () => { - const authdCtxMgr = await ArcGISContextManager.create({ - authentication: MOCK_AUTH, - currentUser: { - username: "casey", - } as unknown as PortalModule.IUser, - portal: { - name: "DC R&D Center", - id: "BRXFAKE", - urlKey: "fake-org", - } as unknown as PortalModule.IPortal, - portalUrl: "https://myserver.com", - }); const createRegistrationSpy = spyOn( registrationModule, "createRegistration" @@ -405,44 +400,26 @@ describe("HubEvents edit module", () => { role: registrationModule.RegistrationRole.ATTENDEE, type: registrationModule.EventAttendanceType.IN_PERSON, }; - await createHubEventRegistration( - data, - authdCtxMgr.context.hubRequestOptions - ); + await createHubEventRegistration(data, context.hubRequestOptions); expect(createRegistrationSpy).toHaveBeenCalledWith({ data, - ...authdCtxMgr.context.hubRequestOptions, + ...context.hubRequestOptions, }); }); }); describe("deleteHubEventRegistration", () => { it("calls deleteRegistration", async () => { - const authdCtxMgr = await ArcGISContextManager.create({ - authentication: MOCK_AUTH, - currentUser: { - username: "casey", - } as unknown as PortalModule.IUser, - portal: { - name: "DC R&D Center", - id: "BRXFAKE", - urlKey: "fake-org", - } as unknown as PortalModule.IPortal, - portalUrl: "https://myserver.com", - }); const deleteRegistrationSpy = spyOn( registrationModule, "deleteRegistration" ).and.callFake(() => { return Promise.resolve(); }); - await deleteHubEventRegistration( - "0o1", - authdCtxMgr.context.hubRequestOptions - ); + await deleteHubEventRegistration("0o1", context.hubRequestOptions); expect(deleteRegistrationSpy).toHaveBeenCalledWith({ registrationId: "0o1", - ...authdCtxMgr.context.hubRequestOptions, + ...context.hubRequestOptions, }); }); }); diff --git a/packages/common/test/events/fetch.test.ts b/packages/common/test/events/fetch.test.ts index a7314d39827..1178dc16db1 100644 --- a/packages/common/test/events/fetch.test.ts +++ b/packages/common/test/events/fetch.test.ts @@ -31,6 +31,7 @@ describe("HubEvent fetch module:", () => { allDay: false, allowRegistration: true, attendanceType: [EventAttendanceType.IN_PERSON], + associations: [], categories: [], editGroups: [], endDateTime: new Date().toISOString(), @@ -65,6 +66,9 @@ describe("HubEvent fetch module:", () => { expect(getEventSpy).toHaveBeenCalledTimes(1); expect(getEventSpy).toHaveBeenCalledWith({ eventId: "123", + data: { + include: "associations", + }, ...authdCtxMgr.context.hubRequestOptions, }); expect(res.name).toEqual("my event"); @@ -87,6 +91,7 @@ describe("HubEvent fetch module:", () => { access: EventAccess.PRIVATE, allDay: false, allowRegistration: true, + associations: [], attendanceType: [EventAttendanceType.IN_PERSON], categories: [], editGroups: [], @@ -122,6 +127,9 @@ describe("HubEvent fetch module:", () => { expect(getEventSpy).toHaveBeenCalledTimes(1); expect(getEventSpy).toHaveBeenCalledWith({ eventId: "123", + data: { + include: "associations", + }, ...authdCtxMgr.context.hubRequestOptions, }); expect(res.name).toEqual("my event"); diff --git a/packages/common/test/search/_internal/hubEventsHelpers/processFilters.test.ts b/packages/common/test/search/_internal/hubEventsHelpers/processFilters.test.ts index a55241a4cd0..41765d47a6e 100644 --- a/packages/common/test/search/_internal/hubEventsHelpers/processFilters.test.ts +++ b/packages/common/test/search/_internal/hubEventsHelpers/processFilters.test.ts @@ -184,6 +184,17 @@ const MULTI_SELECT_FILTERS: IFilter[] = [ }, ], }, + { + operation: "OR", + predicates: [ + { + endDateRange: { + from: 1714376800000, + to: 1714463199999, + }, + }, + ], + }, ]; const SINGLE_SELECT_FILTERS: IFilter[] = [ @@ -261,6 +272,17 @@ const SINGLE_SELECT_FILTERS: IFilter[] = [ }, ], }, + { + operation: "OR", + predicates: [ + { + endDateRange: { + from: 1714376800000, + to: 1714463199999, + }, + }, + ], + }, ]; describe("processFilters", () => { @@ -273,6 +295,8 @@ describe("processFilters", () => { access: "public,org,private", attendanceTypes: "online,in_person", status: "planned,canceled", + endDateTimeAfter: "2024-04-29T07:46:40.000Z", + endDateTimeBefore: "2024-04-30T07:46:39.999Z", startDateTimeAfter: "2024-04-28T04:00:00.000Z", startDateTimeBefore: "2024-04-29T03:59:59.999Z", entityIds: "entity1,entity2", @@ -331,6 +355,8 @@ describe("processFilters", () => { access: "public", attendanceTypes: "online", status: "planned", + endDateTimeAfter: "2024-04-29T07:46:40.000Z", + endDateTimeBefore: "2024-04-30T07:46:39.999Z", startDateTimeAfter: "2024-04-28T04:00:00.000Z", startDateTimeBefore: "2024-04-29T03:59:59.999Z", createdByIds: "owner", diff --git a/packages/common/test/search/_internal/hubEventsHelpers/processOptions.test.ts b/packages/common/test/search/_internal/hubEventsHelpers/processOptions.test.ts index 08b2c5b38a2..93c8b676c47 100644 --- a/packages/common/test/search/_internal/hubEventsHelpers/processOptions.test.ts +++ b/packages/common/test/search/_internal/hubEventsHelpers/processOptions.test.ts @@ -42,6 +42,10 @@ describe("processOptions", () => { sortBy: EventSort.title, sortOrder: EventSortOrder.asc, }); + expect(processOptions({ sortField: "startDate" })).toEqual({ + sortBy: EventSort.startDateTime, + sortOrder: EventSortOrder.asc, + }); }); it("should process sortOrder", () => { expect(processOptions({ sortOrder: "desc" })).toEqual({ diff --git a/packages/common/test/search/_internal/hubSearchEvents.test.ts b/packages/common/test/search/_internal/hubSearchEvents.test.ts index a07e439682c..80ad0f7b84d 100644 --- a/packages/common/test/search/_internal/hubSearchEvents.test.ts +++ b/packages/common/test/search/_internal/hubSearchEvents.test.ts @@ -11,8 +11,6 @@ import { IEvent, IPagedEventResponse, IUser, - RegistrationRole, - RegistrationStatus, } from "../../../src/events/api/orval/api/orval-events"; import { IQuery } from "../../../src/search/types/IHubCatalog"; import { IHubSearchOptions } from "../../../src/search/types/IHubSearchOptions"; @@ -86,23 +84,6 @@ describe("hubSearchEvents", () => { }, readGroups: ["readGroup1Id"], recurrence: null, - registrations: [ - { - createdAt: "2024-04-19T12:15:07.222Z", - createdById: "t_miller", - eventId: "event1Id", - id: "52123", - permission: { - canDelete: false, - canEdit: false, - }, - role: RegistrationRole.ATTENDEE, - status: RegistrationStatus.ACCEPTED, - type: EventAttendanceType.IN_PERSON, - updatedAt: "2024-04-19T12:15:07.222Z", - userId: "a_brown", - }, - ], startDateTime: "2040-07-15T17:00:00.000Z", startDate: "2040-07-15", startTime: "13:00:00", @@ -142,23 +123,6 @@ describe("hubSearchEvents", () => { }, readGroups: ["readGroup2Id"], recurrence: null, - registrations: [ - { - createdAt: "2024-04-21T11:15:07.222Z", - createdById: "t_miller", - eventId: "event2Id", - id: "52124", - permission: { - canDelete: false, - canEdit: false, - }, - role: RegistrationRole.ATTENDEE, - status: RegistrationStatus.ACCEPTED, - type: EventAttendanceType.VIRTUAL, - updatedAt: "2024-04-21T11:15:07.222Z", - userId: "b_arnold", - }, - ], startDateTime: "2030-07-15T17:00:00.000Z", startDate: "2030-07-15", startTime: "10:00:00", @@ -208,38 +172,6 @@ describe("hubSearchEvents", () => { }, readGroups: ["readGroup3Id"], recurrence: null, - registrations: [ - { - createdAt: "2024-06-21T11:15:07.222Z", - createdById: "c_boyd", - eventId: "event3Id", - id: "52125", - permission: { - canDelete: false, - canEdit: false, - }, - role: RegistrationRole.ATTENDEE, - status: RegistrationStatus.ACCEPTED, - type: EventAttendanceType.VIRTUAL, - updatedAt: "2024-06-21T11:15:07.222Z", - userId: "c_boyd", - }, - { - createdAt: "2024-07-21T11:15:07.222Z", - createdById: "a_burns", - eventId: "event3Id", - id: "52126", - permission: { - canDelete: false, - canEdit: false, - }, - role: RegistrationRole.ATTENDEE, - status: RegistrationStatus.ACCEPTED, - type: EventAttendanceType.IN_PERSON, - updatedAt: "2024-07-21T11:15:07.222Z", - userId: "a_burns", - }, - ], startDateTime: "2030-05-15T16:00:00.000Z", startDate: "2030-05-15", startTime: "10:00:00", @@ -318,7 +250,7 @@ describe("hubSearchEvents", () => { data: { ...processedFilters, ...processedOptions, - include: "creator,registrations", + include: "creator", }, }); expect(eventToSearchResultSpy).toHaveBeenCalledTimes(2); @@ -363,7 +295,7 @@ describe("hubSearchEvents", () => { data: { ...processedFilters, ...processedOptions2, - include: "creator,registrations", + include: "creator", }, }, ],