Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0899481
Use newer Elasticsearch methods for events endpoint
axlewin Feb 6, 2026
009f20b
Use global properties for content index and hiding unpublished/test e…
axlewin Feb 9, 2026
394111b
Remove duplicated past events filtering
axlewin Feb 9, 2026
d86be08
Remove showInactiveOnly from events endpoint
axlewin Feb 10, 2026
30fe408
Update test
axlewin Feb 10, 2026
2e8cf86
Allow more specific event date filtering in search instructions
axlewin Feb 10, 2026
96355a4
Filter events by end date, not start date
axlewin Feb 10, 2026
a1faadd
Use IsaacSearchInstructionBuilder for events overview endpoint
axlewin Feb 10, 2026
1497f03
Use IsaacSearchInstructionBuilder for events mapping endpoint
axlewin Feb 10, 2026
1491478
Show nofilter events for staff
axlewin Feb 10, 2026
0444566
Fix "one month ago" calculation
axlewin Feb 10, 2026
90be5dd
Fix checkstyle warnings
axlewin Feb 10, 2026
56811a0
Move event search/filter logic from facade to manager
axlewin Feb 11, 2026
5eb6228
Fix event fetching in promote booking method
axlewin Feb 11, 2026
c877741
Add base search instruction builder for git content manager
axlewin Feb 11, 2026
402f551
Manage showing nofilter content via search instruction builder method
axlewin Feb 11, 2026
53d5dc7
Remove user check from events map data methods
axlewin Feb 11, 2026
b1092f9
Remove unused user account manager
axlewin Feb 11, 2026
51ee898
Fix checkstyle warning
axlewin Feb 11, 2026
4405273
Remove unused content visibility settings
axlewin Feb 11, 2026
4d9b2cf
Update javadoc
axlewin Feb 12, 2026
4a8ab57
Remove duplicated (and now unused) constant
jsharkey13 Feb 12, 2026
a36b5e7
Merge branch 'main' into hotfix/events-es-methods
jsharkey13 Feb 13, 2026
a53a694
Use existing Enum for content index type
jsharkey13 Feb 13, 2026
0fbf35a
Propagate Deprecated annotations on search methods
jsharkey13 Feb 13, 2026
e8b2ff3
Move search provider interaction from events manager to content manager
axlewin Feb 13, 2026
84880d1
Return error not empty response on invalid filters
jsharkey13 Feb 16, 2026
8c55244
Fix odd events double-sorting in opposite orders
jsharkey13 Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
477 changes: 64 additions & 413 deletions src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java

Large diffs are not rendered by default.

409 changes: 409 additions & 0 deletions src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventsManager.java

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions src/main/java/uk/ac/cam/cl/dtg/segue/api/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,6 @@ public enum SegueServerLogType implements LogType {
public static final String CATEGORIES_FIELDNAME = "categories";
public static final String LEVEL_FIELDNAME = "level";
public static final String SUMMARY_FIELDNAME = "summary";
public static final String DATE_FIELDNAME = "date";
public static final String ADDRESS_PSEUDO_FIELDNAME = "address";
public static final String[] ADDRESS_PATH_FIELDNAME = {"location", "address"};
public static final String[] ADDRESS_FIELDNAMES = {"addressLine1", "addressLine2", "town", "county", "postalCode"};
Expand Down Expand Up @@ -503,7 +502,7 @@ public enum SegueServerLogType implements LogType {
* Enum to represent filter values for event management.
*/
public enum EventFilterOption {
FUTURE, RECENT, PAST
FUTURE, RECENT, PAST, ALL
}

public static final String ID_SEPARATOR = "|";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import uk.ac.cam.cl.dtg.segue.api.Constants;
import uk.ac.cam.cl.dtg.segue.database.GitDb;
import uk.ac.cam.cl.dtg.segue.search.AbstractFilterInstruction;
import uk.ac.cam.cl.dtg.segue.search.BooleanInstruction;
import uk.ac.cam.cl.dtg.segue.search.ISearchProvider;
import uk.ac.cam.cl.dtg.segue.search.IsaacSearchInstructionBuilder;
import uk.ac.cam.cl.dtg.segue.search.IsaacSearchInstructionBuilder.Priority;
Expand Down Expand Up @@ -81,8 +82,6 @@
public class GitContentManager {
private static final Logger log = LoggerFactory.getLogger(GitContentManager.class);

private static final String CONTENT_TYPE = "content";

private final GitDb database;
private final ContentMapper mapper;
private final ContentSubclassMapper contentSubclassMapper;
Expand Down Expand Up @@ -255,7 +254,8 @@ public final Content getContentDOById(final String id, final boolean failQuietly

ResultsWrapper<String> rawResults = searchProvider.termSearch(
contentIndex,
CONTENT_TYPE, id,
CONTENT_INDEX_TYPE.CONTENT.toString(),
id,
Constants.ID_FIELDNAME + "." + Constants.UNPROCESSED_SEARCH_FIELD_SUFFIX, 0, 1,
getBaseFilters());
List<Content> searchResults = contentSubclassMapper
Expand Down Expand Up @@ -312,7 +312,7 @@ public ResultsWrapper<ContentDTO> getUnsafeCachedContentDTOsMatchingIds(final Co

ResultsWrapper<String> searchHits = this.searchProvider.termSearch(
contentIndex,
CONTENT_TYPE,
CONTENT_INDEX_TYPE.CONTENT.toString(),
null,
null,
startIndex,
Expand Down Expand Up @@ -386,7 +386,7 @@ public final ResultsWrapper<ContentDTO> siteWideSearch(

// Event specific queries
.searchFor(new SearchInField(Constants.ADDRESS_PSEUDO_FIELDNAME, searchTerms))
.includePastEvents(false);
.setEventFilterOption(EventFilterOption.FUTURE);

// If no search terms were provided, sort by ascending alphabetical order of title.
Map<String, Constants.SortOrder> sortOrder = null;
Expand All @@ -400,7 +400,7 @@ public final ResultsWrapper<ContentDTO> siteWideSearch(

ResultsWrapper<String> searchHits = searchProvider.nestedMatchSearch(
contentIndex,
CONTENT_TYPE,
CONTENT_INDEX_TYPE.CONTENT.toString(),
startIndex,
limit,
searchInstructionBuilder.build(),
Expand Down Expand Up @@ -518,7 +518,7 @@ public final ResultsWrapper<ContentDTO> questionSearch(

ResultsWrapper<String> searchHits = searchProvider.nestedMatchSearch(
contentIndex,
CONTENT_TYPE,
CONTENT_INDEX_TYPE.CONTENT.toString(),
startIndex,
limit,
searchInstructionBuilder.build(),
Expand All @@ -531,19 +531,22 @@ public final ResultsWrapper<ContentDTO> questionSearch(
return new ResultsWrapper<>(contentSubclassMapper.getDTOByDOList(searchResults), searchHits.getTotalResults());
}

@Deprecated
public final ResultsWrapper<ContentDTO> findByFieldNames(
final List<BooleanSearchClause> fieldsToMatch, final Integer startIndex, final Integer limit
) throws ContentManagerException {
return this.findByFieldNames(fieldsToMatch, startIndex, limit, null);
}

@Deprecated
public final ResultsWrapper<ContentDTO> findByFieldNames(
final List<BooleanSearchClause> fieldsToMatch, final Integer startIndex,
final Integer limit, @Nullable final Map<String, Constants.SortOrder> sortInstructions
) throws ContentManagerException {
return this.findByFieldNames(fieldsToMatch, startIndex, limit, sortInstructions, null);
}

@Deprecated
public final ResultsWrapper<ContentDTO> findByFieldNames(
final List<BooleanSearchClause> fieldsToMatch, final Integer startIndex, final Integer limit,
@Nullable final Map<String, Constants.SortOrder> sortInstructions,
Expand All @@ -569,8 +572,9 @@ public final ResultsWrapper<ContentDTO> findByFieldNames(
newFilterInstructions.putAll(this.getBaseFilters());
}

ResultsWrapper<String> searchHits = searchProvider.matchSearch(contentIndex, CONTENT_TYPE, fieldsToMatch,
startIndex, limit, newSortInstructions, newFilterInstructions);
ResultsWrapper<String> searchHits = searchProvider.matchSearch(contentIndex,
CONTENT_INDEX_TYPE.CONTENT.toString(), fieldsToMatch, startIndex, limit,
newSortInstructions, newFilterInstructions);

// setup object mapper to use pre-configured deserializer module.
// Required to deal with type polymorphism
Expand Down Expand Up @@ -600,7 +604,8 @@ public final ResultsWrapper<ContentDTO> findByFieldNamesRandomOrder(

ResultsWrapper<String> searchHits;
searchHits = searchProvider.randomisedMatchSearch(
contentIndex, CONTENT_TYPE, fieldsToMatch, startIndex, limit, randomSeed, this.getBaseFilters());
contentIndex, CONTENT_INDEX_TYPE.CONTENT.toString(), fieldsToMatch, startIndex, limit,
randomSeed, this.getBaseFilters());

// setup object mapper to use pre-configured deserializer module.
// Required to deal with type polymorphism
Expand Down Expand Up @@ -799,6 +804,38 @@ public String getCurrentContentSHA() {
}
}

/**
* Get a search instruction builder initialised with the base configuration for this content manager.
*
* @return a base search instruction builder.
*/
public IsaacSearchInstructionBuilder getBaseSearchInstructionBuilder() {
return new IsaacSearchInstructionBuilder(
searchProvider, this.showOnlyPublishedContent, this.hideRegressionTestContent, true);
}

/**
* Search for content that matches a given instruction and map the hits to DTOs.
*
* @param instruction - the {@link BooleanInstruction} to search with.
* @param startIndex - the initial index for the first result.
* @param limit - the maximum number of results to return.
* @param randomSeed - the random seed to use for the search.
* @param sortInstructions - map of sorting functions to use in ElasticSearch query.
* @return a ResultsWrapper containing the matching content as DTOs and the total number of results.
* @throws ContentManagerException
*/
public ResultsWrapper<ContentDTO> nestedMatchSearch(final BooleanInstruction instruction, final Integer startIndex,
final Integer limit, final Long randomSeed,
final Map<String, Constants. SortOrder> sortInstructions)
throws ContentManagerException {
ResultsWrapper<String> searchHits = this.searchProvider.nestedMatchSearch(contentIndex,
CONTENT_INDEX_TYPE.CONTENT.toString(), startIndex, limit, instruction, randomSeed, sortInstructions);
List<Content> searchResults = this.contentSubclassMapper.mapFromStringListToContentList(searchHits.getResults());
List<ContentDTO> dtoResults = this.contentSubclassMapper.getDTOByDOList(searchResults);
return new ResultsWrapper<>(dtoResults, searchHits.getTotalResults());
}

/**
* Returns the basic filter configuration.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public interface ISearchProvider {
* - the map of how to sort each field of interest.
* @return Results
*/
@Deprecated
ResultsWrapper<String> matchSearch(
final String indexBase, final String indexType,
final List<GitContentManager.BooleanSearchClause> fieldsToMatch, final int startIndex, final int limit,
Expand Down Expand Up @@ -160,6 +161,7 @@ ResultsWrapper<String> termSearch(
* - post search filter instructions e.g. remove content of a certain type.
* @return results in a random order for a given match search.
*/
@Deprecated
ResultsWrapper<String> randomisedMatchSearch(
String indexBase, String indexType, List<GitContentManager.BooleanSearchClause> fieldsToMatch,
int startIndex, int limit, Long randomSeed, Map<String, AbstractFilterInstruction> filterInstructions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ public class IsaacSearchInstructionBuilder {

private final boolean includeOnlyPublishedContent;
private final boolean excludeRegressionTestContent;
private final boolean excludeNofilterContent;
private boolean excludeNofilterContent;
private boolean excludeSupersededContent;
private boolean includePastEvents;
private Constants.EventFilterOption eventFilterOption;

private Set<String> includedContentTypes;
private Set<String> priorityIncludedContentTypes;
Expand Down Expand Up @@ -119,7 +119,7 @@ public IsaacSearchInstructionBuilder(final ISearchProvider searchProvider,
this.excludeRegressionTestContent = excludeRegressionTestContent;

this.excludeNofilterContent = excludeNofilterContent;
this.includePastEvents = false;
this.eventFilterOption = Constants.EventFilterOption.FUTURE;
this.excludeSupersededContent = false;
}

Expand Down Expand Up @@ -201,14 +201,14 @@ public IsaacSearchInstructionBuilder searchFor(final SearchInField searchInField
}

/**
* Sets whether to return events where the date field contains a date in the past. Defaults to false, and has no
* effect if the event content type is excluded.
* Sets a filter for events in a given date range (future/recent/past/all). Defaults to future, and has no effect if
* the event content type is excluded.
*
* @param includePastEvents Whether to include past events.
* @param eventFilterOption The date option to filter events by.
* @return This {@link IsaacSearchInstructionBuilder}, to allow chained operations.
*/
public IsaacSearchInstructionBuilder includePastEvents(final boolean includePastEvents) {
this.includePastEvents = includePastEvents;
public IsaacSearchInstructionBuilder setEventFilterOption(final Constants.EventFilterOption eventFilterOption) {
this.eventFilterOption = eventFilterOption;
return this;
}

Expand All @@ -217,6 +217,17 @@ public IsaacSearchInstructionBuilder excludeSupersededContent(final boolean excl
return this;
}

/**
* Sets whether to include content tagged with "nofilter" in the results. Defaults to excluding such content.
*
* @param includeHiddenContent Whether to include nofilter content in the results.
* @return This IsaacSearchInstructionBuilder, to allow chained operations.
*/
public IsaacSearchInstructionBuilder includeHiddenContent(final boolean includeHiddenContent) {
this.excludeNofilterContent = !includeHiddenContent;
return this;
}

/**
* Builds and returns the final BooleanInstruction reflecting the builder's settings.
*
Expand Down Expand Up @@ -248,12 +259,23 @@ public BooleanInstruction build() {
contentInstruction.must(new MatchInstruction(Constants.TAGS_FIELDNAME, SEARCHABLE_TAG));
}

// Optionally add instruction to match only events that have not yet taken place
if (contentType.equals(EVENT_TYPE) && !includePastEvents) {
// Optionally add instructions to match only events in a particular date range
if (contentType.equals(EVENT_TYPE)) {
LocalDate today = LocalDate.now();
long now = today.atStartOfDay(ZoneId.systemDefault()).toEpochSecond()
* Constants.EVENT_DATE_EPOCH_MULTIPLIER;
contentInstruction.must(new RangeInstruction<Long>(Constants.DATE_FIELDNAME).greaterThanOrEqual(now));
// Default to showing only future events
if (null == this.eventFilterOption || this.eventFilterOption == Constants.EventFilterOption.FUTURE) {
contentInstruction.must(new RangeInstruction<Long>(ENDDATE_FIELDNAME).greaterThanOrEqual(now));
} else if (this.eventFilterOption == Constants.EventFilterOption.RECENT) {
long oneMonthAgo = today.minusMonths(1).atStartOfDay(ZoneId.systemDefault()).toEpochSecond()
* Constants.EVENT_DATE_EPOCH_MULTIPLIER;
contentInstruction.must(new RangeInstruction<Long>(ENDDATE_FIELDNAME).greaterThanOrEqual(oneMonthAgo));
contentInstruction.must(new RangeInstruction<Long>(ENDDATE_FIELDNAME).lessThanOrEqual(now));
} else if (this.eventFilterOption == Constants.EventFilterOption.PAST) {
contentInstruction.must(new RangeInstruction<Long>(ENDDATE_FIELDNAME).lessThanOrEqual(now));
}
// else eventFilterOption == ALL, so don't filter
}

// Apply instructions to search for specific terms in specific fields
Expand All @@ -276,7 +298,7 @@ public BooleanInstruction build() {
this.searchesInFields = new ArrayList<>();
this.includedContentTypes = Sets.newHashSet();
this.priorityIncludedContentTypes = Sets.newHashSet();
this.includePastEvents = false;
this.eventFilterOption = Constants.EventFilterOption.FUTURE;

return masterInstruction;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.testcontainers.utility.MountableFile;
import uk.ac.cam.cl.dtg.isaac.api.managers.AssignmentManager;
import uk.ac.cam.cl.dtg.isaac.api.managers.EventBookingManager;
import uk.ac.cam.cl.dtg.isaac.api.managers.EventsManager;
import uk.ac.cam.cl.dtg.isaac.api.managers.FastTrackManger;
import uk.ac.cam.cl.dtg.isaac.api.managers.GameManager;
import uk.ac.cam.cl.dtg.isaac.api.managers.QuizAssignmentManager;
Expand Down Expand Up @@ -137,6 +138,7 @@ public class AbstractIsaacIntegrationTest {
protected static GameManager gameManager;
protected static GroupManager groupManager;
protected static EventBookingManager eventBookingManager;
protected static EventsManager eventsManager;
protected static ILogManager logManager;
protected static GitContentManager contentManager;
protected static UserAssociationManager userAssociationManager;
Expand Down Expand Up @@ -304,6 +306,7 @@ public static void setUpClass() throws Exception {
userAssociationManager = new UserAssociationManager(pgAssociationDataManager, userAccountManager, groupManager);
PgTransactionManager pgTransactionManager = new PgTransactionManager(postgresSqlDb);
eventBookingManager = new EventBookingManager(bookingPersistanceManager, emailManager, userAssociationManager, properties, groupManager, userAccountManager, pgTransactionManager);
eventsManager = new EventsManager(eventBookingManager, contentManager, mainMapper);
assignmentManager = new AssignmentManager(assignmentPersistenceManager, groupManager, new EmailService(properties, emailManager, groupManager, userAccountManager, mailGunEmailManager), gameManager, properties);
schoolListReader = createNiceMock(SchoolListReader.class);

Expand Down
6 changes: 3 additions & 3 deletions src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,19 @@ public class EventsFacadeIT extends IsaacIntegrationTest {
@BeforeEach
public void setUp() {
// Get an instance of the facade to test
eventsFacade = new EventsFacade(properties, logManager, eventBookingManager, userAccountManager, contentManager,
eventsFacade = new EventsFacade(properties, logManager, eventsManager, eventBookingManager, userAccountManager,
userAssociationManager, groupManager, userAccountManager, schoolListReader, mainMapper);
}

@Test
// GET /events -> EventFacade::getEvents(request, tags, startIndex, limit, sortOrder, showActiveOnly, showInactiveOnly, showMyBookingsOnly, showReservationsOnly, showStageOnly)
// GET /events -> EventFacade::getEvents(request, tags, startIndex, limit, sortOrder, showActiveOnly, showMyBookingsOnly, showReservationsOnly, showStageOnly)
public void getEventsTest() {
// Create an anonymous request (this is a mocked object)
HttpServletRequest request = createRequestWithCookies(new Cookie[]{});
replay(request);

// Execute the method (endpoint) to be tested
Response response = eventsFacade.getEvents(request, null, 0, 10, null, null, null, null, null, null);
Response response = eventsFacade.getEvents(request, null, 0, 10, null, null, null, null, null);
// Check that the request succeeded
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
// Fetch the entity object. This can be anything, so we declare it first as Object
Expand Down
Loading