Skip to content

Commit e047e5f

Browse files
Merge pull request #34 from derekphilipau/dev
Dev
2 parents fa66d26 + 976e50c commit e047e5f

File tree

18 files changed

+575
-109
lines changed

18 files changed

+575
-109
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { ingester } from '@/lib/import/ingesters/met/collectionsIngester';
2+
import { ArtworkDocument } from '@/types/document';
3+
4+
const mockMetDocument = {
5+
'Object Number': '04.1a–c',
6+
'Is Highlight': true,
7+
'Is Timeline Work': true,
8+
'Is Public Domain': false,
9+
'Object ID': 35,
10+
'Gallery Number': 706,
11+
Department: 'The American Wing',
12+
AccessionYear: 1904,
13+
'Object Name': 'Vase',
14+
Title: 'The Adams Vase',
15+
Culture: 'American',
16+
Period: null,
17+
Dynasty: null,
18+
Reign: null,
19+
Portfolio: null,
20+
'Constituent ID': 108316253,
21+
'Artist Role': 'Designer|Manufacturer',
22+
'Artist Prefix': 'Designed by|Manufactured by',
23+
'Artist Display Name': 'Paulding Farnham|Tiffany & Co.',
24+
'Artist Display Bio': '1859–1927|1837–present',
25+
'Artist Suffix': ' | ',
26+
'Artist Alpha Sort': 'Farnham, Paulding|Tiffany & Co.',
27+
'Artist Nationality': 'American| ',
28+
'Artist Begin Date': '1859 |1837 ',
29+
'Artist End Date': '1927 |9999 ',
30+
'Artist Gender': '|',
31+
'Artist ULAN URL':
32+
'http://vocab.getty.edu/page/ulan/500336597|http://vocab.getty.edu/page/ulan/500330306',
33+
'Artist Wikidata URL':
34+
'https://www.wikidata.org/wiki/Q13476260|https://www.wikidata.org/wiki/Q1066858',
35+
'Object Date': '1893–95',
36+
'Object Begin Date': 1893,
37+
'Object End Date': 1895,
38+
Medium:
39+
'Gold, amethysts, spessartites, tourmalines, fresh water pearls, quartzes, rock crystal, and enamel',
40+
Dimensions:
41+
'Overall: 19 7/16 x 13 x 9 1/4 in. (49.4 x 33 x 23.5 cm); 352 oz. 18 dwt. (10977 g) Body: H. 18 7/8 in. (47.9 cm) Cover: 4 1/4 x 4 13/16 in. (10.8 x 12.2 cm); 19 oz. 6 dwt. (600.1 g)',
42+
'Credit Line': 'Gift of Edward D. Adams, 1904',
43+
'Geography Type': 'Made in',
44+
City: 'New York',
45+
State: null,
46+
County: null,
47+
Country: 'United States',
48+
Region: null,
49+
Subregion: null,
50+
Locale: null,
51+
Locus: null,
52+
Excavation: null,
53+
River: null,
54+
Classification: null,
55+
'Rights and Reproduction': null,
56+
'Link Resource': 'http://www.metmuseum.org/art/collection/search/35',
57+
'Object Wikidata URL': 'https://www.wikidata.org/wiki/Q83545838',
58+
'Metadata Date': null,
59+
Repository: 'Metropolitan Museum of Art, New York, NY',
60+
Tags: 'Animals|Garlands|Birds|Men',
61+
'Tags AAT URL':
62+
'http://vocab.getty.edu/page/aat/300249525|http://vocab.getty.edu/page/aat/300167386|http://vocab.getty.edu/page/aat/300266506|http://vocab.getty.edu/page/aat/300025928',
63+
'Tags Wikidata URL':
64+
'https://www.wikidata.org/wiki/Q729|https://www.wikidata.org/wiki/Q756600|https://www.wikidata.org/wiki/Q5113|https://www.wikidata.org/wiki/Q8441',
65+
};
66+
67+
describe('transformDoc', () => {
68+
it('should transform MetDocument into ArtworkDocument', async () => {
69+
const esDoc = await ingester.transform(mockMetDocument) as ArtworkDocument;
70+
71+
expect(esDoc.source).toBe('The Met');
72+
expect(esDoc.id).toBe('35');
73+
expect(esDoc.title).toBe('The Adams Vase');
74+
expect(esDoc.dimensions).toContain('19 7/16 x 13 x 9 1/4 in.');
75+
expect(esDoc.highlight).toBe(true);
76+
expect(esDoc.keywords).toBeDefined();
77+
expect(esDoc.keywords?.length).toBe(4);
78+
expect(esDoc.primaryConstituent?.name).toBe('Paulding Farnham');
79+
});
80+
});

components/search/event-search-checkboxes.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export function EventSearchCheckboxes({ params }: EventSearchCheckboxesProps) {
1212

1313
return (
1414
<div className="flex flex-wrap gap-x-4 gap-y-2">
15-
{/*
1615
<div className="flex items-center space-x-2">
1716
<SearchCheckbox
1817
params={params}
@@ -21,7 +20,6 @@ export function EventSearchCheckboxes({ params }: EventSearchCheckboxesProps) {
2120
label={dict['search.isNow']}
2221
/>
2322
</div>
24-
*/}
2523
<div className="flex items-center space-x-2">
2624
<SearchCheckbox
2725
params={params}

components/timeline/timeline.tsx

Lines changed: 64 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,6 @@ const sourceColors = {
2424
Boston: '#ef4444',
2525
};
2626

27-
// Define your margins
28-
const margin = { top: 40, right: 10, bottom: 10, left: 10 };
29-
30-
// Define the size of your chart
31-
const width = 1200; // You can adjust this as needed
32-
const height = 600; // You can adjust this as needed
33-
// maxDate should be X months from today:
34-
const maxDate = new Date(new Date().setMonth(new Date().getMonth() + 6))
35-
.toISOString()
36-
.split('T')[0];
37-
3827
function getBarText(item: BaseDocument | EventDocument) {
3928
// get item title. if > 40 chars, truncate and add ellipsis:
4029
const myTitle =
@@ -44,8 +33,9 @@ function getBarText(item: BaseDocument | EventDocument) {
4433
return `${myTitle} - ${item.source}`;
4534
}
4635

47-
function getDomainMin(items: (BaseDocument | EventDocument)[]) {
48-
const min = Math.min(
36+
function getDomainMin(items: EventDocument[]) {
37+
if (!(items?.length > 0)) return new Date().getTime();
38+
const min = Math.max(
4939
...items
5040
.filter((item) => item.date)
5141
.map((item) => new Date(item.date || '').getTime())
@@ -54,31 +44,41 @@ function getDomainMin(items: (BaseDocument | EventDocument)[]) {
5444
}
5545

5646
function getDomainMax(items: (BaseDocument | EventDocument)[]) {
47+
const maxDate = new Date(new Date().setMonth(new Date().getMonth() + 6))
48+
.toISOString()
49+
.split('T')[0];
50+
if (!(items?.length > 0)) return new Date().getTime();
5751
const max = Math.max(
5852
...items
59-
.filter((item): item is EventDocument => 'endDate' in item && item.endDate !== undefined)
53+
.filter(
54+
(item): item is EventDocument =>
55+
'endDate' in item && item.endDate !== undefined
56+
)
6057
.map((item) => {
61-
// if item.endDate is greater than 5 years into future, don't use it:
62-
const endDate = new Date(item.endDate || '').getTime();
63-
return endDate >
64-
new Date(
65-
new Date().setFullYear(new Date().getFullYear() + 5)
66-
).getTime()
67-
? new Date(maxDate).getTime()
68-
: endDate;
58+
// if item.endDate is greater than 3 years into future, don't use it:
59+
try {
60+
const endDate = new Date(item.endDate || '').getTime();
61+
return endDate >
62+
new Date(
63+
new Date().setFullYear(new Date().getFullYear() + 20)
64+
).getTime()
65+
? new Date(maxDate).getTime()
66+
: endDate;
67+
} catch (e) {
68+
return 0;
69+
}
6970
})
7071
);
7172
return max;
7273
}
7374

74-
interface TimelineProps {
75-
items: (BaseDocument | EventDocument)[];
76-
}
77-
78-
export function Timeline({ items }: TimelineProps) {
79-
const dict = getDictionary();
80-
// create a new array of items, sorted by location:
81-
const sortedItems = items.sort((a, b) => {
75+
/**
76+
* Sort items by location and sourceId
77+
* @param items Array of items to sort
78+
* @returns Sorted array of items
79+
*/
80+
function getSortedItems(items: (BaseDocument | EventDocument)[]) {
81+
return [...items].sort((a, b) => {
8282
if (a.sourceId && b.sourceId) {
8383
const locationA = a.sourceId && sources[a.sourceId]?.location;
8484
const locationB = b.sourceId && sources[b.sourceId]?.location;
@@ -94,35 +94,44 @@ export function Timeline({ items }: TimelineProps) {
9494
}
9595
return 0;
9696
});
97+
}
9798

98-
// for each item, if endDate > maxTime, set endDate to maxTime
99-
const maxTime = getDomainMax(sortedItems);
100-
const maxDate = format(new Date(maxTime), 'yyyy-MM-dd');
99+
function getMinTimeWithinDomain(item: EventDocument, minTime: number) {
100+
if (item.date) return new Date(item.date).getTime();
101+
return minTime;
102+
}
103+
104+
function getMaxTimeWithinDomain(item: EventDocument, maxTime: number) {
105+
if (item.endDate && new Date(item.endDate).getTime() < maxTime) {
106+
return new Date(item.endDate).getTime();
107+
}
108+
return maxTime;
109+
}
110+
111+
const chartMargin = { top: 40, right: 10, bottom: 10, left: 10 };
112+
const chartWidth = 1200;
113+
const chartHeight = 600;
114+
115+
interface TimelineProps {
116+
items: (BaseDocument | EventDocument)[];
117+
}
118+
119+
export function Timeline({ items }: TimelineProps) {
120+
const dict = getDictionary();
121+
const sortedItems = getSortedItems(items);
101122
const minTime = getDomainMin(sortedItems);
102-
const minDate = format(new Date(minTime), 'yyyy-MM-dd');
103-
sortedItems.forEach((item: EventDocument) => {
104-
// if item.endDate is greater than 5 years into future, don't use it:
105-
if (item.endDate) {
106-
const endDate = new Date(item.endDate).getTime();
107-
if (
108-
endDate >
109-
new Date(new Date().setFullYear(new Date().getFullYear() + 5)).getTime()
110-
) {
111-
item.endDate = format(new Date(maxTime), 'yyyy-MM-dd');
112-
}
113-
}
114-
});
123+
const maxTime = getDomainMax(sortedItems);
115124

116125
const timeScale = scaleLinear({
117-
domain: [getDomainMin(sortedItems), getDomainMax(sortedItems)],
118-
range: [margin.left, width - margin.right], // Now for horizontal
126+
domain: [minTime, maxTime],
127+
range: [chartMargin.left, chartWidth - chartMargin.right], // Now for horizontal
119128
});
120129

121130
const itemScale = scaleBand<string>({
122131
domain: sortedItems
123132
.filter((item) => item.title)
124133
.map((item) => item.title) as string[],
125-
range: [height - margin.bottom, margin.top],
134+
range: [chartHeight - chartMargin.bottom, chartMargin.top],
126135
padding: 0.1,
127136
});
128137

@@ -141,12 +150,12 @@ export function Timeline({ items }: TimelineProps) {
141150
return (
142151
<>
143152
<div className="w-full overflow-x-auto">
144-
<svg ref={svgRef} width={width} height={height}>
153+
<svg ref={svgRef} width={chartWidth} height={chartHeight}>
145154
<Group>
146155
{sortedItems.map((item: EventDocument, i: Key) => {
147156
// Swap the usage of scales for x and y
148-
const startX = timeScale(new Date(item.date || minDate).getTime());
149-
const endX = timeScale(new Date(item.endDate || maxDate).getTime());
157+
const startX = timeScale(getMinTimeWithinDomain(item, minTime));
158+
const endX = timeScale(getMaxTimeWithinDomain(item, maxTime));
150159
const barX = Math.min(startX, endX);
151160
const barWidth = Math.abs(endX - startX);
152161
const barY = itemScale(item.title || '') ?? 0;
@@ -212,7 +221,7 @@ export function Timeline({ items }: TimelineProps) {
212221
);
213222
})}
214223
<AxisTop
215-
top={margin.top}
224+
top={chartMargin.top}
216225
scale={timeScale}
217226
tickFormat={(value: number) => {
218227
const date = new Date(value);
@@ -227,8 +236,8 @@ export function Timeline({ items }: TimelineProps) {
227236
<line
228237
x1={timeScale(currentTime)}
229238
x2={timeScale(currentTime)}
230-
y1={margin.top}
231-
y2={height - margin.bottom}
239+
y1={chartMargin.top}
240+
y2={chartHeight - chartMargin.bottom}
232241
stroke="red"
233242
strokeWidth={2}
234243
/>

config/site.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const siteConfig: SiteConfig = {
5757
'whitney/collectionsIngester',
5858
'met/collectionsIngester',
5959
],
60-
extractors: ['openAiExhibitionsExtractor'],
60+
extractors: [],
6161
exhibitionUrls: [
6262
{
6363
url: 'https://www.moma.org/calendar/exhibitions/',
@@ -226,7 +226,7 @@ export const siteConfig: SiteConfig = {
226226
{
227227
dict: 'index.events',
228228
basePath: 'events',
229-
href: '/events',
229+
href: '/events?isNow=true&f=true',
230230
},
231231
],
232232
links: {

dictionaries/lang/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"search.imageUnavailable": "Image Unavailable",
3232
"search.noResults": "Sorry, we couldn’t find any results matching your criteria.",
3333
"search.didYouMean": "Did you mean:",
34-
"search.isNow": "Now on view",
34+
"search.isNow": "On view",
3535
"search.isShowTimeline": "Show timeline",
3636
"button.openMenu": "Open Menu",
3737
"button.expandFilter": "Expand search filter",

lib/elasticsearch/import.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Client } from '@elastic/elasticsearch';
22
import * as T from '@elastic/elasticsearch/lib/api/types';
33

4+
import { BaseDocument } from '@/types/document';
45
import { art, events, news, terms } from './indices';
56

67
const indices = {
@@ -319,3 +320,32 @@ export function getBulkOperationArray(
319320
method === 'update' ? { doc, doc_as_upsert: true } : { doc },
320321
];
321322
}
323+
324+
/**
325+
* Upsert a document in an index. Doc not guaranteed to contain _id or _index,
326+
* so force those arguments in function signature.
327+
*
328+
* @param client Elasticsearch client.
329+
* @param index Elasticsearch index.
330+
* @param id Elasticsearch document id.
331+
* @param document Elasticsearch document.
332+
*/
333+
export async function upsertDocument(
334+
client: Client,
335+
index: string,
336+
id: string,
337+
document: BaseDocument
338+
) {
339+
const doc = { ...document };
340+
delete doc._id;
341+
delete doc._index;
342+
await client.update({
343+
index,
344+
id,
345+
body: {
346+
doc,
347+
doc_as_upsert: true,
348+
},
349+
refresh: true,
350+
});
351+
}

lib/elasticsearch/search/search.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import { getClient } from '../client';
1212
import { getElasticsearchIndices, type SearchParams } from './searchParams';
1313
import {
1414
addColorQuery,
15-
addQueryAggs,
1615
addDefaultQueryBoolDateRange,
17-
addQueryBoolYearRange,
16+
addQueryAggs,
17+
addQueryBoolDateRange,
1818
addQueryBoolFilterTerms,
19+
addQueryBoolYearRange,
1920
} from './searchQueryBuilder';
2021
import { getTerm, terms } from './terms';
2122

@@ -74,6 +75,9 @@ export async function search(
7475
addDefaultQueryBoolDateRange(esQuery, searchParams);
7576
// Multi-index search boosts news and events
7677
esQuery.indices_boost = [{ news: 1.5 }, { events: 1.5 }, { art: 1 }];
78+
} else if (searchParams.index === 'events' && searchParams.isNow) {
79+
// Events search has special date range filter
80+
addQueryBoolDateRange(esQuery, new Date(), new Date());
7781
} else {
7882
addQueryBoolYearRange(esQuery, searchParams);
7983
}

0 commit comments

Comments
 (0)