Skip to content

Commit

Permalink
feat(hub-common): add support for configuring entity references for E… (
Browse files Browse the repository at this point in the history
  • Loading branch information
rweber-esri committed Sep 12, 2024
1 parent 31fb3ea commit 9435382
Show file tree
Hide file tree
Showing 28 changed files with 905 additions and 417 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const EventGalleryCardSchema: IConfigurationSchema = {
},
corners: {
type: "string",
enum: ["squared", "rounded"],
default: "squared",
enum: ["square", "round"],
default: "square",
},
shadow: {
type: "string",
Expand Down
9 changes: 7 additions & 2 deletions packages/common/src/core/types/IHubEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Record<string, string>>;
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
Expand Down
8 changes: 8 additions & 0 deletions packages/common/src/events/_internal/EventSchemaCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 8 additions & 2 deletions packages/common/src/events/_internal/EventSchemaEdit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions packages/common/src/events/_internal/EventUiSchemaCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -170,6 +171,11 @@ export const buildUiSchema = async (
],
},
},
buildReferencedContentSchema(
i18nScope,
context,
`{{${i18nScope}.fields.referencedContent.label:translate}}`
),
],
};
};
12 changes: 7 additions & 5 deletions packages/common/src/events/_internal/EventUiSchemaEdit.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -19,10 +20,6 @@ export const buildUiSchema = async (
options: Partial<IHubEvent>,
context: IArcGISContext
): Promise<IUiSchema> => {
const minStartEndDate = getDatePickerDate(
new Date(),
(options as IHubEvent).timeZone
);
return {
type: "Layout",
elements: [
Expand Down Expand Up @@ -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`,
Expand Down
6 changes: 6 additions & 0 deletions packages/common/src/events/_internal/PropertyMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
34 changes: 34 additions & 0 deletions packages/common/src/events/_internal/buildEventAssociations.ts
Original file line number Diff line number Diff line change
@@ -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<ICreateEventAssociation[]> {
// 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[];
}
Original file line number Diff line number Diff line change
@@ -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",
},
],
},
};
}
4 changes: 3 additions & 1 deletion packages/common/src/events/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export function buildDefaultEventEntity(): Partial<IHubEvent> {
onlineCapacityType: HubEventCapacityType.Unlimited,
onlineDetails: null,
onlineUrl: null,
references: [],
referencedContentIds: [],
referencedContentIdsByType: [],
schemaVersion: 1,
tags: [],
readGroupIds: [],
Expand All @@ -57,6 +58,7 @@ export function buildDefaultEventRecord(): Partial<IEvent> {
access: EventAccess.PRIVATE,
allDay: false,
allowRegistration: true,
associations: [],
attendanceType: [EventAttendanceType.IN_PERSON],
categories: [],
inPersonCapacity: null,
Expand Down
17 changes: 16 additions & 1 deletion packages/common/src/events/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
IRegistration,
RegistrationRole,
} from "./api";
import { buildEventAssociations } from "./_internal/buildEventAssociations";

export interface IHubCreateEventRegistration {
eventId: string;
Expand All @@ -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<IHubEvent>,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions packages/common/src/events/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,21 @@ export async function processFilters(
startDateRange[0].from
).toISOString();
}
const endDateRange = getPredicateValuesByKey<IDateRange<string | number>>(
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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 9435382

Please sign in to comment.