Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #34

Merged
merged 3 commits into from
Dec 12, 2023
Merged

Dev #34

Show file tree
Hide file tree
Changes from all commits
Commits
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
80 changes: 80 additions & 0 deletions __tests__/lib/import/ingesters/met/collectionsIngester.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { ingester } from '@/lib/import/ingesters/met/collectionsIngester';
import { ArtworkDocument } from '@/types/document';

const mockMetDocument = {
'Object Number': '04.1a–c',
'Is Highlight': true,
'Is Timeline Work': true,
'Is Public Domain': false,
'Object ID': 35,
'Gallery Number': 706,
Department: 'The American Wing',
AccessionYear: 1904,
'Object Name': 'Vase',
Title: 'The Adams Vase',
Culture: 'American',
Period: null,
Dynasty: null,
Reign: null,
Portfolio: null,
'Constituent ID': 108316253,
'Artist Role': 'Designer|Manufacturer',
'Artist Prefix': 'Designed by|Manufactured by',
'Artist Display Name': 'Paulding Farnham|Tiffany & Co.',
'Artist Display Bio': '1859–1927|1837–present',
'Artist Suffix': ' | ',
'Artist Alpha Sort': 'Farnham, Paulding|Tiffany & Co.',
'Artist Nationality': 'American| ',
'Artist Begin Date': '1859 |1837 ',
'Artist End Date': '1927 |9999 ',
'Artist Gender': '|',
'Artist ULAN URL':
'http://vocab.getty.edu/page/ulan/500336597|http://vocab.getty.edu/page/ulan/500330306',
'Artist Wikidata URL':
'https://www.wikidata.org/wiki/Q13476260|https://www.wikidata.org/wiki/Q1066858',
'Object Date': '1893–95',
'Object Begin Date': 1893,
'Object End Date': 1895,
Medium:
'Gold, amethysts, spessartites, tourmalines, fresh water pearls, quartzes, rock crystal, and enamel',
Dimensions:
'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)',
'Credit Line': 'Gift of Edward D. Adams, 1904',
'Geography Type': 'Made in',
City: 'New York',
State: null,
County: null,
Country: 'United States',
Region: null,
Subregion: null,
Locale: null,
Locus: null,
Excavation: null,
River: null,
Classification: null,
'Rights and Reproduction': null,
'Link Resource': 'http://www.metmuseum.org/art/collection/search/35',
'Object Wikidata URL': 'https://www.wikidata.org/wiki/Q83545838',
'Metadata Date': null,
Repository: 'Metropolitan Museum of Art, New York, NY',
Tags: 'Animals|Garlands|Birds|Men',
'Tags AAT URL':
'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',
'Tags Wikidata URL':
'https://www.wikidata.org/wiki/Q729|https://www.wikidata.org/wiki/Q756600|https://www.wikidata.org/wiki/Q5113|https://www.wikidata.org/wiki/Q8441',
};

describe('transformDoc', () => {
it('should transform MetDocument into ArtworkDocument', async () => {
const esDoc = await ingester.transform(mockMetDocument) as ArtworkDocument;

expect(esDoc.source).toBe('The Met');
expect(esDoc.id).toBe('35');
expect(esDoc.title).toBe('The Adams Vase');
expect(esDoc.dimensions).toContain('19 7/16 x 13 x 9 1/4 in.');
expect(esDoc.highlight).toBe(true);
expect(esDoc.keywords).toBeDefined();
expect(esDoc.keywords?.length).toBe(4);
expect(esDoc.primaryConstituent?.name).toBe('Paulding Farnham');
});
});
2 changes: 0 additions & 2 deletions components/search/event-search-checkboxes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export function EventSearchCheckboxes({ params }: EventSearchCheckboxesProps) {

return (
<div className="flex flex-wrap gap-x-4 gap-y-2">
{/*
<div className="flex items-center space-x-2">
<SearchCheckbox
params={params}
Expand All @@ -21,7 +20,6 @@ export function EventSearchCheckboxes({ params }: EventSearchCheckboxesProps) {
label={dict['search.isNow']}
/>
</div>
*/}
<div className="flex items-center space-x-2">
<SearchCheckbox
params={params}
Expand Down
119 changes: 64 additions & 55 deletions components/timeline/timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@ const sourceColors = {
Boston: '#ef4444',
};

// Define your margins
const margin = { top: 40, right: 10, bottom: 10, left: 10 };

// Define the size of your chart
const width = 1200; // You can adjust this as needed
const height = 600; // You can adjust this as needed
// maxDate should be X months from today:
const maxDate = new Date(new Date().setMonth(new Date().getMonth() + 6))
.toISOString()
.split('T')[0];

function getBarText(item: BaseDocument | EventDocument) {
// get item title. if > 40 chars, truncate and add ellipsis:
const myTitle =
Expand All @@ -44,8 +33,9 @@ function getBarText(item: BaseDocument | EventDocument) {
return `${myTitle} - ${item.source}`;
}

function getDomainMin(items: (BaseDocument | EventDocument)[]) {
const min = Math.min(
function getDomainMin(items: EventDocument[]) {
if (!(items?.length > 0)) return new Date().getTime();
const min = Math.max(
...items
.filter((item) => item.date)
.map((item) => new Date(item.date || '').getTime())
Expand All @@ -54,31 +44,41 @@ function getDomainMin(items: (BaseDocument | EventDocument)[]) {
}

function getDomainMax(items: (BaseDocument | EventDocument)[]) {
const maxDate = new Date(new Date().setMonth(new Date().getMonth() + 6))
.toISOString()
.split('T')[0];
if (!(items?.length > 0)) return new Date().getTime();
const max = Math.max(
...items
.filter((item): item is EventDocument => 'endDate' in item && item.endDate !== undefined)
.filter(
(item): item is EventDocument =>
'endDate' in item && item.endDate !== undefined
)
.map((item) => {
// if item.endDate is greater than 5 years into future, don't use it:
const endDate = new Date(item.endDate || '').getTime();
return endDate >
new Date(
new Date().setFullYear(new Date().getFullYear() + 5)
).getTime()
? new Date(maxDate).getTime()
: endDate;
// if item.endDate is greater than 3 years into future, don't use it:
try {
const endDate = new Date(item.endDate || '').getTime();
return endDate >
new Date(
new Date().setFullYear(new Date().getFullYear() + 20)
).getTime()
? new Date(maxDate).getTime()
: endDate;
} catch (e) {
return 0;
}
})
);
return max;
}

interface TimelineProps {
items: (BaseDocument | EventDocument)[];
}

export function Timeline({ items }: TimelineProps) {
const dict = getDictionary();
// create a new array of items, sorted by location:
const sortedItems = items.sort((a, b) => {
/**
* Sort items by location and sourceId
* @param items Array of items to sort
* @returns Sorted array of items
*/
function getSortedItems(items: (BaseDocument | EventDocument)[]) {
return [...items].sort((a, b) => {
if (a.sourceId && b.sourceId) {
const locationA = a.sourceId && sources[a.sourceId]?.location;
const locationB = b.sourceId && sources[b.sourceId]?.location;
Expand All @@ -94,35 +94,44 @@ export function Timeline({ items }: TimelineProps) {
}
return 0;
});
}

// for each item, if endDate > maxTime, set endDate to maxTime
const maxTime = getDomainMax(sortedItems);
const maxDate = format(new Date(maxTime), 'yyyy-MM-dd');
function getMinTimeWithinDomain(item: EventDocument, minTime: number) {
if (item.date) return new Date(item.date).getTime();
return minTime;
}

function getMaxTimeWithinDomain(item: EventDocument, maxTime: number) {
if (item.endDate && new Date(item.endDate).getTime() < maxTime) {
return new Date(item.endDate).getTime();
}
return maxTime;
}

const chartMargin = { top: 40, right: 10, bottom: 10, left: 10 };
const chartWidth = 1200;
const chartHeight = 600;

interface TimelineProps {
items: (BaseDocument | EventDocument)[];
}

export function Timeline({ items }: TimelineProps) {
const dict = getDictionary();
const sortedItems = getSortedItems(items);
const minTime = getDomainMin(sortedItems);
const minDate = format(new Date(minTime), 'yyyy-MM-dd');
sortedItems.forEach((item: EventDocument) => {
// if item.endDate is greater than 5 years into future, don't use it:
if (item.endDate) {
const endDate = new Date(item.endDate).getTime();
if (
endDate >
new Date(new Date().setFullYear(new Date().getFullYear() + 5)).getTime()
) {
item.endDate = format(new Date(maxTime), 'yyyy-MM-dd');
}
}
});
const maxTime = getDomainMax(sortedItems);

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

const itemScale = scaleBand<string>({
domain: sortedItems
.filter((item) => item.title)
.map((item) => item.title) as string[],
range: [height - margin.bottom, margin.top],
range: [chartHeight - chartMargin.bottom, chartMargin.top],
padding: 0.1,
});

Expand All @@ -141,12 +150,12 @@ export function Timeline({ items }: TimelineProps) {
return (
<>
<div className="w-full overflow-x-auto">
<svg ref={svgRef} width={width} height={height}>
<svg ref={svgRef} width={chartWidth} height={chartHeight}>
<Group>
{sortedItems.map((item: EventDocument, i: Key) => {
// Swap the usage of scales for x and y
const startX = timeScale(new Date(item.date || minDate).getTime());
const endX = timeScale(new Date(item.endDate || maxDate).getTime());
const startX = timeScale(getMinTimeWithinDomain(item, minTime));
const endX = timeScale(getMaxTimeWithinDomain(item, maxTime));
const barX = Math.min(startX, endX);
const barWidth = Math.abs(endX - startX);
const barY = itemScale(item.title || '') ?? 0;
Expand Down Expand Up @@ -212,7 +221,7 @@ export function Timeline({ items }: TimelineProps) {
);
})}
<AxisTop
top={margin.top}
top={chartMargin.top}
scale={timeScale}
tickFormat={(value: number) => {
const date = new Date(value);
Expand All @@ -227,8 +236,8 @@ export function Timeline({ items }: TimelineProps) {
<line
x1={timeScale(currentTime)}
x2={timeScale(currentTime)}
y1={margin.top}
y2={height - margin.bottom}
y1={chartMargin.top}
y2={chartHeight - chartMargin.bottom}
stroke="red"
strokeWidth={2}
/>
Expand Down
4 changes: 2 additions & 2 deletions config/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const siteConfig: SiteConfig = {
'whitney/collectionsIngester',
'met/collectionsIngester',
],
extractors: ['openAiExhibitionsExtractor'],
extractors: [],
exhibitionUrls: [
{
url: 'https://www.moma.org/calendar/exhibitions/',
Expand Down Expand Up @@ -226,7 +226,7 @@ export const siteConfig: SiteConfig = {
{
dict: 'index.events',
basePath: 'events',
href: '/events',
href: '/events?isNow=true&f=true',
},
],
links: {
Expand Down
2 changes: 1 addition & 1 deletion dictionaries/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"search.imageUnavailable": "Image Unavailable",
"search.noResults": "Sorry, we couldn’t find any results matching your criteria.",
"search.didYouMean": "Did you mean:",
"search.isNow": "Now on view",
"search.isNow": "On view",
"search.isShowTimeline": "Show timeline",
"button.openMenu": "Open Menu",
"button.expandFilter": "Expand search filter",
Expand Down
30 changes: 30 additions & 0 deletions lib/elasticsearch/import.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Client } from '@elastic/elasticsearch';
import * as T from '@elastic/elasticsearch/lib/api/types';

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

const indices = {
Expand Down Expand Up @@ -319,3 +320,32 @@ export function getBulkOperationArray(
method === 'update' ? { doc, doc_as_upsert: true } : { doc },
];
}

/**
* Upsert a document in an index. Doc not guaranteed to contain _id or _index,
* so force those arguments in function signature.
*
* @param client Elasticsearch client.
* @param index Elasticsearch index.
* @param id Elasticsearch document id.
* @param document Elasticsearch document.
*/
export async function upsertDocument(
client: Client,
index: string,
id: string,
document: BaseDocument
) {
const doc = { ...document };
delete doc._id;
delete doc._index;
await client.update({
index,
id,
body: {
doc,
doc_as_upsert: true,
},
refresh: true,
});
}
8 changes: 6 additions & 2 deletions lib/elasticsearch/search/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import { getClient } from '../client';
import { getElasticsearchIndices, type SearchParams } from './searchParams';
import {
addColorQuery,
addQueryAggs,
addDefaultQueryBoolDateRange,
addQueryBoolYearRange,
addQueryAggs,
addQueryBoolDateRange,
addQueryBoolFilterTerms,
addQueryBoolYearRange,
} from './searchQueryBuilder';
import { getTerm, terms } from './terms';

Expand Down Expand Up @@ -74,6 +75,9 @@ export async function search(
addDefaultQueryBoolDateRange(esQuery, searchParams);
// Multi-index search boosts news and events
esQuery.indices_boost = [{ news: 1.5 }, { events: 1.5 }, { art: 1 }];
} else if (searchParams.index === 'events' && searchParams.isNow) {
// Events search has special date range filter
addQueryBoolDateRange(esQuery, new Date(), new Date());
} else {
addQueryBoolYearRange(esQuery, searchParams);
}
Expand Down
Loading