-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into addressables-unpublisher
- Loading branch information
Showing
42 changed files
with
3,540 additions
and
3,099 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/* | ||
Before the end-of-year closure, we test that the date picker dropdown in the Request item dialog is going to display the correct options | ||
to ensure that users can only requests items when they can be available, on a day the library is open | ||
Once the Modified opening times have been added to Prismic and published, | ||
set dateNow as the date to be tested and run this script with | ||
yarn check_holiday_closures | ||
*/ | ||
|
||
import { DateTime } from 'luxon'; | ||
|
||
import { getNextOpeningDates } from '@weco/content-api/src/controllers/utils'; | ||
import { getElasticClient } from '@weco/content-common/services/elasticsearch'; | ||
import { | ||
ElasticsearchVenue, | ||
NextOpeningDate, | ||
Venue, | ||
} from '@weco/content-common/types/venue'; | ||
|
||
// set this to the date to be tested, here and in utils.ts | ||
// eg. if you want to see what the dates on the date picker will be on December 14, 2024 at 8:30am | ||
// const dateNow = new Date("2024-12-14T08:30:00.000Z"); | ||
const dateNow = new Date(); | ||
|
||
const formatDate = (openingDay: NextOpeningDate) => | ||
openingDay.open | ||
? new Date(openingDay.open).toUTCString().slice(0, 11) + '\n' | ||
: ''; | ||
|
||
const compareDates = (library: NextOpeningDate, deepstore: NextOpeningDate) => { | ||
const libraryDate = | ||
library.open && DateTime.fromJSDate(new Date(library.open)).startOf('day'); | ||
const deepstoreDate = | ||
deepstore.open && | ||
DateTime.fromJSDate(new Date(deepstore.open)).startOf('day'); | ||
if (libraryDate === undefined || deepstoreDate === undefined) { | ||
throw new Error('One of these dates is undefined!'); | ||
} else { | ||
return libraryDate > deepstoreDate; | ||
} | ||
}; | ||
|
||
// the 2 functions below reproduce the logic used in the catalogue-api/items to generate available dates | ||
|
||
const applyItemsApiLibraryLogic = (openingTimes: NextOpeningDate[]) => { | ||
// the library is open today if the 1st date in NextOpeningDate[] is the same as today | ||
const isLibraryOpenToday = | ||
new Date(dateNow).getDate() === | ||
(openingTimes[0].open && new Date(openingTimes[0].open).getDate()); | ||
if (dateNow.getHours() < 10 || !isLibraryOpenToday) { | ||
return openingTimes.slice(1, -1).map(formatDate); | ||
} else { | ||
return openingTimes.slice(2, -1).map(formatDate); | ||
} | ||
}; | ||
|
||
const applyItemsApiDeepstoreLogic = ( | ||
libraryOpeningTimes: NextOpeningDate[], | ||
deepstoreOpeningTimes: NextOpeningDate[] | ||
) => { | ||
const firstDeepstoreAvailability = deepstoreOpeningTimes.slice(10, -1)[0]; | ||
const subsequentLibraryAvailabilities = libraryOpeningTimes.filter( | ||
libraryOpeningTime => { | ||
return compareDates(libraryOpeningTime, firstDeepstoreAvailability); | ||
} | ||
); | ||
return subsequentLibraryAvailabilities.map(formatDate); | ||
}; | ||
|
||
const run = async () => { | ||
const elasticClient = await getElasticClient({ | ||
serviceName: 'api', | ||
pipelineDate: '2024-12-10', | ||
hostEndpointAccess: 'public', | ||
}); | ||
|
||
const searchResponse = await elasticClient.search<ElasticsearchVenue>({ | ||
index: 'venues', | ||
_source: ['display'], | ||
query: { | ||
bool: { | ||
filter: [{ terms: { 'filter.title': ['library', 'deepstore'] } }], | ||
}, | ||
}, | ||
}); | ||
const venuesData = searchResponse.hits.hits.flatMap(hit => | ||
hit._source ? [hit._source.display] : [] | ||
); | ||
|
||
const { | ||
regularOpeningDays: libraryRegularOpeningDays, | ||
exceptionalClosedDays: libraryHolidayclosures, | ||
} = venuesData.find(venue => venue.title === 'Library') as Venue; | ||
const { | ||
regularOpeningDays: deepstoreRegularOpeningDays, | ||
exceptionalClosedDays: deepstoreHolidayclosures, | ||
} = venuesData.find(venue => venue.title === 'Deepstore') as Venue; | ||
|
||
const onsiteItemsPickup = applyItemsApiLibraryLogic( | ||
getNextOpeningDates(libraryRegularOpeningDays, libraryHolidayclosures) | ||
).slice(0, 12); | ||
const offsiteItemsPickup = applyItemsApiDeepstoreLogic( | ||
getNextOpeningDates(libraryRegularOpeningDays, libraryHolidayclosures), | ||
getNextOpeningDates(deepstoreRegularOpeningDays, deepstoreHolidayclosures) | ||
).slice(0, 12); | ||
|
||
const library = `Onsite items requested on ${dateNow} will be available on: \n${onsiteItemsPickup.join('\r')}`; | ||
const deepstore = `Deepstore items requested on ${dateNow} will be available on: \n${offsiteItemsPickup.join('\r')}`; | ||
console.log(library); | ||
console.log(deepstore); | ||
}; | ||
|
||
run(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { errors as elasticErrors } from '@elastic/elasticsearch'; | ||
import { RequestHandler } from 'express'; | ||
import asyncHandler from 'express-async-handler'; | ||
|
||
import { Config } from '@weco/content-api/config'; | ||
import { ifDefined } from '@weco/content-api/src/helpers'; | ||
import { resultListResponse } from '@weco/content-api/src/helpers/responses'; | ||
import { addressablesQuery } from '@weco/content-api/src/queries/addressables'; | ||
import { Clients, Displayable } from '@weco/content-api/src/types'; | ||
import { ResultList } from '@weco/content-api/src/types/responses'; | ||
|
||
import { HttpError } from './error'; | ||
import { paginationElasticBody, PaginationQueryParameters } from './pagination'; | ||
|
||
type QueryParams = { | ||
query?: string; | ||
} & PaginationQueryParameters; | ||
|
||
type AddressablesHandler = RequestHandler< | ||
never, | ||
ResultList, | ||
never, | ||
QueryParams | ||
>; | ||
|
||
const addressablesController = ( | ||
clients: Clients, | ||
config: Config | ||
): AddressablesHandler => { | ||
const index = config.addressablesIndex; | ||
const resultList = resultListResponse(config); | ||
|
||
return asyncHandler(async (req, res) => { | ||
const { query: queryString } = req.query; | ||
|
||
try { | ||
const searchResponse = await clients.elastic.search<Displayable>({ | ||
index, | ||
_source: ['display'], | ||
query: { | ||
bool: { | ||
must: ifDefined(queryString, addressablesQuery), | ||
}, | ||
}, | ||
|
||
...paginationElasticBody(req.query), | ||
}); | ||
|
||
res.status(200).json(resultList(req, searchResponse)); | ||
} catch (e) { | ||
if ( | ||
e instanceof elasticErrors.ResponseError && | ||
// This is an error we see from very long (spam) queries which contain | ||
// many many terms and so overwhelm the multi_match query. The check | ||
// for length is a heuristic so that if we get legitimate `too_many_nested_clauses` | ||
// errors, we're still alerted to them | ||
e.message.includes('too_many_nested_clauses') && | ||
encodeURIComponent(queryString || '').length > 2000 | ||
) { | ||
throw new HttpError({ | ||
status: 400, | ||
label: 'Bad Request', | ||
description: | ||
'Your query contained too many terms, please try again with a simpler query', | ||
}); | ||
} | ||
throw e; | ||
} | ||
}); | ||
}; | ||
|
||
export default addressablesController; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; | ||
|
||
export const addressablesQuery = ( | ||
queryString: string | ||
): QueryDslQueryContainer => ({ | ||
multi_match: { | ||
query: queryString, | ||
fields: [ | ||
'id', | ||
'uid', | ||
'query.title.*^100', | ||
'query.contributors.*^10', | ||
'query.contributors.keyword^100', | ||
'query.body.*', | ||
'query.description.*', | ||
], | ||
operator: 'or', | ||
type: 'cross_fields', | ||
minimum_should_match: '-25%', | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { mockedApi } from './fixtures/api'; | ||
|
||
describe('GET /all', () => { | ||
it('returns a list of documents', async () => { | ||
const docs = Array.from({ length: 10 }).map((_, i) => ({ | ||
id: `id-${i}`, | ||
display: { | ||
title: `test doc ${i}`, | ||
}, | ||
})); | ||
const api = mockedApi(docs); | ||
|
||
const response = await api.get(`/all`); | ||
expect(response.statusCode).toBe(200); | ||
expect(response.body.results).toStrictEqual(docs.map(d => d.display)); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.