Skip to content

Commit fb95a5e

Browse files
authored
WEBDEV-7737 Support month resolution in date picker (#476)
* Upgrade search service version * Generalize handling of histogram aggregations * Upgrade to alpha date picker * Use correct bin snapping & tooltips on date picker * Update unit tests * Restore correct loading behavior for histogram * Use date instead of year in filter map * Upgrade off alpha date picker version
1 parent e435870 commit fb95a5e

12 files changed

+274
-78
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@
2626
"@internetarchive/analytics-manager": "^0.1.4",
2727
"@internetarchive/feature-feedback": "^1.0.0",
2828
"@internetarchive/field-parsers": "^1.0.0",
29-
"@internetarchive/histogram-date-range": "^1.2.1",
29+
"@internetarchive/histogram-date-range": "^1.3.0",
3030
"@internetarchive/ia-activity-indicator": "^0.0.6",
3131
"@internetarchive/ia-dropdown": "^1.3.10",
3232
"@internetarchive/iaux-item-metadata": "^1.0.5",
3333
"@internetarchive/infinite-scroller": "^1.0.1",
3434
"@internetarchive/modal-manager": "^2.0.1",
35-
"@internetarchive/search-service": "^2.3.2",
35+
"@internetarchive/search-service": "^2.4.0",
3636
"@internetarchive/shared-resize-observer": "^0.2.0",
3737
"@lit/localize": "^0.12.2",
3838
"dompurify": "^3.2.4",

src/collection-browser.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,8 +1138,7 @@ export class CollectionBrowser
11381138
.resizeObserver=${this.resizeObserver}
11391139
.searchType=${this.searchType}
11401140
.aggregations=${this.dataSource.aggregations}
1141-
.fullYearsHistogramAggregation=${this.dataSource
1142-
.yearHistogramAggregation}
1141+
.histogramAggregation=${this.dataSource.histogramAggregation}
11431142
.minSelectedDate=${this.minSelectedDate}
11441143
.maxSelectedDate=${this.maxSelectedDate}
11451144
.selectedFacets=${this.selectedFacets}
@@ -1158,7 +1157,7 @@ export class CollectionBrowser
11581157
.isTvSearch=${shouldUseTvInterface}
11591158
?collapsableFacets=${this.mobileView}
11601159
?facetsLoading=${this.facetsLoading}
1161-
?fullYearAggregationLoading=${this.facetsLoading}
1160+
?histogramAggregationLoading=${this.facetsLoading}
11621161
?suppressMediatypeFacets=${this.suppressMediatypeFacets}
11631162
@facetClick=${this.facetClickHandler}
11641163
@facetsChanged=${this.facetsChanged}

src/collection-facets.ts

Lines changed: 103 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type { FeatureFeedbackServiceInterface } from '@internetarchive/feature-f
2929
import type { RecaptchaManagerInterface } from '@internetarchive/recaptcha-manager';
3030
import type { AnalyticsManagerInterface } from '@internetarchive/analytics-manager';
3131
import type { SharedResizeObserverInterface } from '@internetarchive/shared-resize-observer';
32+
import type { BinSnappingInterval } from '@internetarchive/histogram-date-range';
3233
import chevronIcon from './assets/img/icons/chevron';
3334
import expandIcon from './assets/img/icons/expand';
3435
import {
@@ -50,10 +51,6 @@ import type {
5051
CollectionTitles,
5152
PageSpecifierParams,
5253
} from './data-source/models';
53-
import './collection-facets/more-facets-content';
54-
import './collection-facets/facets-template';
55-
import './collection-facets/facet-tombstone-row';
56-
import './expanded-date-picker';
5754
import {
5855
analyticsActions,
5956
analyticsCategories,
@@ -65,6 +62,12 @@ import {
6562
updateSelectedFacetBucket,
6663
} from './utils/facet-utils';
6764

65+
import '@internetarchive/histogram-date-range';
66+
import './collection-facets/more-facets-content';
67+
import './collection-facets/facets-template';
68+
import './collection-facets/facet-tombstone-row';
69+
import './expanded-date-picker';
70+
6871
@customElement('collection-facets')
6972
export class CollectionFacets extends LitElement {
7073
@property({ type: Object }) searchService?: SearchServiceInterface;
@@ -73,7 +76,7 @@ export class CollectionFacets extends LitElement {
7376

7477
@property({ type: Object }) aggregations?: Record<string, Aggregation>;
7578

76-
@property({ type: Object }) fullYearsHistogramAggregation?: Aggregation;
79+
@property({ type: Object }) histogramAggregation?: Aggregation;
7780

7881
@property({ type: String }) minSelectedDate?: string;
7982

@@ -83,7 +86,7 @@ export class CollectionFacets extends LitElement {
8386

8487
@property({ type: Boolean }) facetsLoading = false;
8588

86-
@property({ type: Boolean }) fullYearAggregationLoading = false;
89+
@property({ type: Boolean }) histogramAggregationLoading = false;
8790

8891
@property({ type: Object }) selectedFacets?: SelectedFacets;
8992

@@ -164,7 +167,7 @@ export class CollectionFacets extends LitElement {
164167
return html`
165168
<div id="container" class=${containerClasses}>
166169
${this.showHistogramDatePicker &&
167-
(this.fullYearsHistogramAggregation || this.fullYearAggregationLoading)
170+
(this.histogramAggregation || this.histogramAggregationLoading)
168171
? html`
169172
<section
170173
class="facet-group"
@@ -230,15 +233,63 @@ export class CollectionFacets extends LitElement {
230233
});
231234
}
232235

236+
/**
237+
* Properties to pass into the date-picker histogram component
238+
*/
239+
private get histogramProps() {
240+
const { histogramAggregation: aggregation } = this;
241+
if (!aggregation) return undefined;
242+
243+
// Normalize some properties from the raw aggregation
244+
const firstYear =
245+
aggregation.first_bucket_year ?? aggregation.first_bucket_key;
246+
const lastYear =
247+
aggregation.last_bucket_year ?? aggregation.last_bucket_key;
248+
if (firstYear == null || lastYear == null) return undefined; // We at least need a start/end year defined
249+
250+
const firstMonth = aggregation.first_bucket_month ?? 1;
251+
const lastMonth = aggregation.last_bucket_month ?? 12;
252+
253+
const yearInterval = aggregation.interval ?? 1;
254+
const monthInterval = aggregation.interval_in_months ?? 12;
255+
256+
const zeroPadMonth = (month: number) => month.toString().padStart(2, '0');
257+
258+
// Format the min/max dates appropriately
259+
const minDate = this.isTvSearch
260+
? `${firstYear}-${zeroPadMonth(firstMonth)}`
261+
: `${firstYear}`;
262+
263+
const maxDate = this.isTvSearch
264+
? `${lastYear}-${zeroPadMonth(lastMonth + monthInterval - 1)}`
265+
: `${lastYear + yearInterval - 1}`;
266+
267+
const hasMonths = this.isTvSearch && monthInterval < 12;
268+
return {
269+
buckets: aggregation.buckets as number[],
270+
dateFormat: this.isTvSearch ? 'YYYY-MM' : 'YYYY',
271+
tooltipDateFormat: hasMonths ? 'MMM YYYY' : 'YYYY',
272+
binSnapping: (hasMonths ? 'month' : 'year') as BinSnappingInterval,
273+
minDate,
274+
maxDate,
275+
};
276+
}
277+
233278
/**
234279
* Opens a modal dialog containing an enlarged version of the date picker.
235280
*/
236281
private showDatePickerModal(): void {
237-
const { fullYearsHistogramAggregation } = this;
238-
const minDate = fullYearsHistogramAggregation?.first_bucket_key;
239-
const maxDate = fullYearsHistogramAggregation?.last_bucket_key;
240-
const buckets = fullYearsHistogramAggregation?.buckets as number[];
241-
const dateFormat = this.isTvSearch ? 'YYYY-MM' : 'YYYY';
282+
const { histogramProps } = this;
283+
if (!histogramProps) return;
284+
285+
const {
286+
buckets,
287+
dateFormat,
288+
tooltipDateFormat,
289+
binSnapping,
290+
minDate,
291+
maxDate,
292+
} = histogramProps;
242293

243294
// Because the modal manager does not clear its DOM content after being closed,
244295
// it may try to render the exact same date picker template when it is reopened.
@@ -264,6 +315,8 @@ export class CollectionFacets extends LitElement {
264315
.minSelectedDate=${this.minSelectedDate}
265316
.maxSelectedDate=${this.maxSelectedDate}
266317
.dateFormat=${dateFormat}
318+
.tooltipDateFormat=${tooltipDateFormat}
319+
.binSnapping=${binSnapping}
267320
.buckets=${buckets}
268321
.modalManager=${this.modalManager}
269322
.analyticsHandler=${this.analyticsHandler}
@@ -328,30 +381,42 @@ export class CollectionFacets extends LitElement {
328381
: nothing;
329382
}
330383

331-
private get histogramTemplate() {
332-
const { fullYearsHistogramAggregation } = this;
333-
const minDate = fullYearsHistogramAggregation?.first_bucket_key;
334-
const maxDate = fullYearsHistogramAggregation?.last_bucket_key;
335-
const dateFormat = this.isTvSearch ? 'YYYY-MM' : 'YYYY';
336-
return this.fullYearAggregationLoading
337-
? html`<div class="histogram-loading-indicator">&hellip;</div>` // Ellipsis block
338-
: html`
339-
<histogram-date-range
340-
class=${this.isTvSearch ? 'wide-inputs' : nothing}
341-
.minDate=${minDate}
342-
.maxDate=${maxDate}
343-
.minSelectedDate=${this.minSelectedDate ?? minDate}
344-
.maxSelectedDate=${this.maxSelectedDate ?? maxDate}
345-
.updateDelay=${100}
346-
.dateFormat=${dateFormat}
347-
missingDataMessage="..."
348-
.width=${this.collapsableFacets && this.contentWidth
349-
? this.contentWidth
350-
: 180}
351-
.bins=${fullYearsHistogramAggregation?.buckets as number[]}
352-
@histogramDateRangeUpdated=${this.histogramDateRangeUpdated}
353-
></histogram-date-range>
354-
`;
384+
private get histogramTemplate(): TemplateResult | typeof nothing {
385+
if (this.histogramAggregationLoading) {
386+
return html` <div class="histogram-loading-indicator">&hellip;</div> `;
387+
}
388+
389+
const { histogramProps } = this;
390+
if (!histogramProps) return nothing;
391+
392+
const {
393+
buckets,
394+
dateFormat,
395+
tooltipDateFormat,
396+
binSnapping,
397+
minDate,
398+
maxDate,
399+
} = histogramProps;
400+
401+
return html`
402+
<histogram-date-range
403+
class=${this.isTvSearch ? 'wide-inputs' : nothing}
404+
.minDate=${minDate}
405+
.maxDate=${maxDate}
406+
.minSelectedDate=${this.minSelectedDate ?? minDate}
407+
.maxSelectedDate=${this.maxSelectedDate ?? maxDate}
408+
.updateDelay=${100}
409+
.dateFormat=${dateFormat}
410+
.tooltipDateFormat=${tooltipDateFormat}
411+
.binSnapping=${binSnapping}
412+
.bins=${buckets}
413+
missingDataMessage="..."
414+
.width=${this.collapsableFacets && this.contentWidth
415+
? this.contentWidth
416+
: 180}
417+
@histogramDateRangeUpdated=${this.histogramDateRangeUpdated}
418+
></histogram-date-range>
419+
`;
355420
}
356421
357422
/**
@@ -533,8 +598,8 @@ export class CollectionFacets extends LitElement {
533598
private get aggregationFacetGroups(): FacetGroup[] {
534599
const facetGroups: FacetGroup[] = [];
535600
Object.entries(this.aggregations ?? []).forEach(([key, aggregation]) => {
536-
// the year_histogram data is in a different format so can't be handled here
537-
if (key === 'year_histogram') return;
601+
// the year_histogram and date_histogram data is in a different format so can't be handled here
602+
if (['year_histogram', 'date_histogram'].includes(key)) return;
538603
539604
const option = key as FacetOption;
540605
const title = facetTitles[option];

src/collection-facets/smart-facets/smart-facet-bar.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ export class SmartFacetBar extends LitElement {
193193
const agg = this.lastAggregations[key];
194194
if (!agg) continue;
195195
if (agg.buckets.length === 0) continue;
196-
if (['lending', 'year_histogram'].includes(key)) continue;
196+
if (['lending', 'year_histogram', 'date_histogram'].includes(key))
197+
continue;
197198
if (typeof agg.buckets[0] === 'number') continue;
198199

199200
if (

src/data-source/collection-browser-data-source-interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ export interface CollectionBrowserDataSourceInterface
7878
readonly aggregations?: Record<string, Aggregation>;
7979

8080
/**
81-
* The `year_histogram` aggregation retrieved for the current search.
81+
* The `year_histogram` or `date_histogram` aggregation retrieved for the current search.
8282
*/
83-
readonly yearHistogramAggregation?: Aggregation;
83+
readonly histogramAggregation?: Aggregation;
8484

8585
/**
8686
* A map from collection identifiers that appear on hits or aggregations for the

src/data-source/collection-browser-data-source.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export class CollectionBrowserDataSource
114114
/**
115115
* @inheritdoc
116116
*/
117-
yearHistogramAggregation?: Aggregation;
117+
histogramAggregation?: Aggregation;
118118

119119
/**
120120
* @inheritdoc
@@ -234,7 +234,7 @@ export class CollectionBrowserDataSource
234234
log('Resetting CB data source');
235235
this.pages = {};
236236
this.aggregations = {};
237-
this.yearHistogramAggregation = undefined;
237+
this.histogramAggregation = undefined;
238238
this.pageElements = undefined;
239239
this.parentCollections = [];
240240
this.previousQueryKey = '';
@@ -700,16 +700,18 @@ export class CollectionBrowserDataSource
700700
selectedCreatorFilter,
701701
} = this.host;
702702

703+
const dateField = this.host.searchType === SearchType.TV ? 'date' : 'year';
704+
703705
if (minSelectedDate) {
704706
builder.addFilter(
705-
'year',
707+
dateField,
706708
minSelectedDate,
707709
FilterConstraint.GREATER_OR_EQUAL,
708710
);
709711
}
710712
if (maxSelectedDate) {
711713
builder.addFilter(
712-
'year',
714+
dateField,
713715
maxSelectedDate,
714716
FilterConstraint.LESS_OR_EQUAL,
715717
);
@@ -1038,8 +1040,10 @@ export class CollectionBrowserDataSource
10381040
}
10391041
}
10401042

1041-
this.yearHistogramAggregation =
1042-
success?.response?.aggregations?.year_histogram;
1043+
this.histogramAggregation =
1044+
this.host.searchType === SearchType.TV
1045+
? aggregations?.date_histogram
1046+
: aggregations?.year_histogram;
10431047

10441048
this.setFacetsLoading(false);
10451049
this.requestHostUpdate();

src/expanded-date-picker.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import { css, html, LitElement, CSSResultGroup, TemplateResult } from 'lit';
22
import { customElement, property } from 'lit/decorators.js';
3+
import { ifDefined } from 'lit/directives/if-defined.js';
34
import { msg } from '@lit/localize';
45
import type { ModalManagerInterface } from '@internetarchive/modal-manager';
56
import type { AnalyticsManagerInterface } from '@internetarchive/analytics-manager';
7+
import { BinSnappingInterval } from '@internetarchive/histogram-date-range';
68
import {
79
analyticsActions,
810
analyticsCategories,
911
} from './utils/analytics-events';
1012

13+
import '@internetarchive/histogram-date-range';
14+
1115
@customElement('expanded-date-picker')
1216
export class ExpandedDatePicker extends LitElement {
1317
@property({ type: String }) minDate?: string;
@@ -22,6 +26,10 @@ export class ExpandedDatePicker extends LitElement {
2226

2327
@property({ type: String }) dateFormat: string = 'YYYY';
2428

29+
@property({ type: String }) tooltipDateFormat?: string;
30+
31+
@property({ type: String }) binSnapping?: BinSnappingInterval;
32+
2533
@property({ type: Object, attribute: false })
2634
modalManager?: ModalManagerInterface;
2735

@@ -38,6 +46,8 @@ export class ExpandedDatePicker extends LitElement {
3846
.minSelectedDate=${this.minSelectedDate ?? this.minDate}
3947
.maxSelectedDate=${this.maxSelectedDate ?? this.maxDate}
4048
.dateFormat=${this.dateFormat}
49+
tooltipDateFormat=${ifDefined(this.tooltipDateFormat)}
50+
binSnapping=${ifDefined(this.binSnapping)}
4151
.updateDelay=${0}
4252
updateWhileFocused
4353
missingDataMessage="..."

0 commit comments

Comments
 (0)