Skip to content

Commit

Permalink
SNAPSHOT: filter results by date range
Browse files Browse the repository at this point in the history
  • Loading branch information
tkohr committed Oct 25, 2024
1 parent 7b660c1 commit 3713c5d
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class AllRecordsComponent {
importRecordButton!: ElementRef
@ViewChild('template') template!: TemplateRef<any>
private overlayRef!: OverlayRef
searchFields = ['user']
searchFields = ['user', 'publicationYear', 'changeDate']
searchText$: Observable<string | null> =
this.searchFacade.searchFilters$.pipe(
map((filters) => ('any' in filters ? (filters['any'] as string) : null))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class MyRecordsComponent implements OnInit {
private importRecordButton!: ElementRef
@ViewChild('template') template!: TemplateRef<any>
private overlayRef!: OverlayRef
searchFields = []
searchFields = ['changeDate']
searchText$: Observable<string | null>

isImportMenuOpen = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ describe('ElasticsearchService', () => {
Org: {
world: true,
},
someDate: {
start: new Date('2021-03-03'),
},
otherDate: {
start: new Date('2020-01-01'),
end: new Date('2020-12-31'),
},
any: 'hello',
},
{},
Expand All @@ -187,6 +194,23 @@ describe('ElasticsearchService', () => {
query: 'Org:("world")',
},
},
{
range: {
someDate: {
gte: '2021-03-03',
format: 'yyyy-MM-dd',
},
},
},
{
range: {
otherDate: {
gte: '2020-01-01',
lte: '2020-12-31',
format: 'yyyy-MM-dd',
},
},
},
{
ids: {
values: ['record-1', 'record-2', 'record-3'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
FieldFilter,
FieldFilters,
FilterAggregationParams,
FiltersAggregationParams,
SortByField,
} from '@geonetwork-ui/common/domain/model/search'
import { METADATA_LANGUAGE } from '../../metadata-language'
Expand All @@ -27,6 +28,9 @@ import {
} from '@geonetwork-ui/api/metadata-converter'
import { LangService } from '@geonetwork-ui/util/i18n'

export type DateRange = { start?: Date; end?: Date }
export type TimestampRange = { start?: number; end?: number }

@Injectable({
providedIn: 'root',
})
Expand Down Expand Up @@ -213,7 +217,10 @@ export class ElasticsearchService {
return this.metadataLang === 'current'
}

private filtersToQueryString(filters: FieldFilters): string {
// TODO: type this
private filtersToQuery(
filters: FieldFilters | FiltersAggregationParams | string
): any[] {
const makeQuery = (filter: FieldFilter): string => {
if (typeof filter === 'string') {
return filter
Expand All @@ -227,13 +234,67 @@ export class ElasticsearchService {
})
.join(' OR ')
}
return Object.keys(filters)
const queryString = Object.keys(filters)
// .filter((fieldname) => !this.isDateRange(JSON.parse(filters[fieldname])))
.filter((fieldname) => fieldname !== 'changeDate') // TODO: make this generic
.filter(
(fieldname) =>
filters[fieldname] && JSON.stringify(filters[fieldname]) !== '{}'
)
.map((fieldname) => `${fieldname}:(${makeQuery(filters[fieldname])})`)
.join(' AND ')
const queryRange = Object.entries(filters)
// .filter(([key, value]) => this.isDateRange(JSON.parse(value)))
.filter(([key, value]) => key === 'changeDate') // TODO: make this generic
.map(([searchField, dateRange]) => {
console.log('dateRange', dateRange)
return {
searchField,
dateRange: this.parseUrlObject(dateRange[0]), // TODO: reading values on app load not working yet
} as unknown as {
searchField: string
dateRange: TimestampRange
}
})[0]
const queryParts = [
queryString && {
query_string: {
query: queryString,
},
},
queryRange &&
queryRange.dateRange && {
range: {
[queryRange.searchField]: {
gte: this.formatDate(queryRange.dateRange.start),
lte: this.formatDate(queryRange.dateRange.end),
format: 'yyyy-MM-dd',
},
},
},
].filter(Boolean)
return queryParts.length > 0 ? queryParts : undefined
}

// TODO: move utility functions to right place
private isDateRange(filter: FieldFilter): boolean {
return typeof filter === 'object' && ('start' in filter || 'end' in filter)
}

private formatDate(timestamp: number): string {
const date = new Date(timestamp)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}

private parseUrlObject(str: string): Record<string, unknown> {
try {
return JSON.parse(str)
} catch (e) {
return null
}
}

private buildPayloadQuery(
Expand Down Expand Up @@ -266,13 +327,9 @@ export class ElasticsearchService {
},
})
}
const queryFilters = this.filtersToQueryString(fieldSearchFilters)
const queryFilters = this.filtersToQuery(fieldSearchFilters)
if (queryFilters) {
filter.push({
query_string: {
query: queryFilters,
},
})
filter.push(...queryFilters)
}
if (uuids) {
filter.push({
Expand Down Expand Up @@ -480,14 +537,7 @@ export class ElasticsearchService {
const filter = aggregation.filters[curr]
return {
...prev,
[curr]: {
query_string: {
query:
typeof filter === 'string'
? filter
: this.filtersToQueryString(filter),
},
},
[curr]: this.filtersToQuery(filter),
}
}, {}),
}
Expand Down
10 changes: 9 additions & 1 deletion libs/common/domain/src/lib/model/search/filter.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,13 @@ import { FieldName } from './field.model'

export type FieldFilterByValues = Record<string, boolean>
export type FieldFilterByExpression = string | number
export type FieldFilter = FieldFilterByExpression | FieldFilterByValues
export type FieldFilterByRange = {
start?: Date
end?: Date
}

export type FieldFilter =
| FieldFilterByExpression
| FieldFilterByValues
| FieldFilterByRange
export type FieldFilters = Record<FieldName, FieldFilter>
6 changes: 5 additions & 1 deletion libs/feature/router/src/lib/default/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ export enum ROUTE_PARAMS {
PUBLISHER = 'publisher', // FIXME: this shouldn't be here as it is a search field
PAGE = '_page',
}
export type SearchRouteParams = Record<string, string | string[] | number>
export type TimestampRange = { start?: number; end?: number }
export type SearchRouteParams = Record<
string,
string | string[] | number | TimestampRange
>
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
<gn-ui-dropdown-multiselect
class="w-full"
[title]="title"
[maxRows]="6"
[choices]="choices$ | async"
[selected]="selected$ | async"
[allowSearch]="true"
(selectValues)="onSelectedValues($event)"
[attr.data-cy-field]="fieldName"
>
</gn-ui-dropdown-multiselect>
<gn-ui-date-range-picker
*ngIf="fieldType === 'dateRange'; else multiselect"
(startDateChange)="onStartDateChange($event)"
(endDateChange)="onEndDateChange($event)"
></gn-ui-date-range-picker>
<ng-template #multiselect>
<gn-ui-dropdown-multiselect
class="w-full"
[title]="title"
[maxRows]="6"
[choices]="choices$ | async"
[selected]="selected$ | async"
[allowSearch]="true"
(selectValues)="onSelectedValues($event)"
[attr.data-cy-field]="fieldName"
>
</gn-ui-dropdown-multiselect>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import { catchError, filter, map, startWith } from 'rxjs/operators'
import { SearchFacade } from '../state/search.facade'
import { SearchService } from '../utils/service/search.service'
import { FieldsService } from '../utils/service/fields.service'
import { FieldAvailableValue, FieldValue } from '../utils/service/fields'
import {
DateRange,
FieldAvailableValue,
FieldType,
FieldValue,
} from '../utils/service/fields'

@Component({
selector: 'gn-ui-filter-dropdown',
Expand All @@ -22,6 +27,8 @@ export class FilterDropdownComponent implements OnInit {
@Input() fieldName: string
@Input() title: string

fieldType: FieldType
dateRange: DateRange
choices$: Observable<Choice[]>
selected$ = this.searchFacade.searchFilters$.pipe(
switchMap((filters) =>
Expand All @@ -46,6 +53,7 @@ export class FilterDropdownComponent implements OnInit {
) {}

ngOnInit() {
this.fieldType = this.fieldsService.getFieldType(this.fieldName)
this.choices$ = this.fieldsService.getAvailableValues(this.fieldName).pipe(
startWith([] as FieldAvailableValue[]),
map((values) =>
Expand All @@ -57,4 +65,21 @@ export class FilterDropdownComponent implements OnInit {
catchError(() => of([]))
)
}

onStartDateChange(start: Date) {
this.dateRange = { ...this.dateRange, start }
}

onEndDateChange(end: Date) {
this.dateRange = { ...this.dateRange, end }
if (this.dateRange.start && this.dateRange.end) {
this.fieldsService
.buildFiltersFromFieldValues({
[this.fieldName]: this.dateRange,
})
.subscribe((filters) => {
return this.searchService.updateFilters(filters)
})
}
}
}
32 changes: 25 additions & 7 deletions libs/feature/search/src/lib/utils/service/fields.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Injectable, Injector } from '@angular/core'
import {
AbstractSearchField,
DateRange,
DateRangeSearchField,
FieldValue,
FullTextSearchField,
IsSpatialSearchField,
Expand All @@ -9,6 +11,7 @@ import {
OrganizationSearchField,
OwnerSearchField,
SimpleSearchField,
TimestampRange,
TranslatedSearchField,
UserSearchField,
} from './fields'
Expand Down Expand Up @@ -87,6 +90,7 @@ export class FieldsService {
'key'
),
user: new UserSearchField(this.injector),
changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
} as Record<string, AbstractSearchField>

get supportedFields() {
Expand All @@ -101,25 +105,37 @@ export class FieldsService {
return this.fields[fieldName].getAvailableValues()
}

private getFiltersForValues(fieldName: string, values: FieldValue[]) {
private getFiltersForValues(
fieldName: string,
values: FieldValue[] | DateRange
) {
return this.fields[fieldName].getFiltersForValues(values)
}
private getValuesForFilters(fieldName: string, filters: FieldFilters) {
return this.fields[fieldName].getValuesForFilter(filters)
}

getFieldType(fieldName: string) {
return this.fields[fieldName].getType()
}

buildFiltersFromFieldValues(
fieldValues: FieldValues
fieldValues: FieldValues | DateRange
): Observable<FieldFilters> {
const fieldNames = Object.keys(fieldValues).filter((fieldName) =>
this.supportedFields.includes(fieldName)
)
if (!fieldNames.length) return of({})
const filtersByField$ = fieldNames.map((fieldName) => {
const values = Array.isArray(fieldValues[fieldName])
? fieldValues[fieldName]
: [fieldValues[fieldName]]
return this.getFiltersForValues(fieldName, values as FieldValue[])
const values =
Array.isArray(fieldValues[fieldName]) ||
Object.keys(fieldValues[fieldName]).length > 1
? fieldValues[fieldName]
: [fieldValues[fieldName]]
return this.getFiltersForValues(
fieldName,
values as FieldValue[] | DateRange
)
})
return forkJoin(filtersByField$).pipe(
map((filters) =>
Expand All @@ -128,7 +144,9 @@ export class FieldsService {
)
}

readFieldValuesFromFilters(filters: FieldFilters): Observable<FieldValues> {
readFieldValuesFromFilters(
filters: FieldFilters
): Observable<FieldValues | TimestampRange> {
const fieldValues$ = this.supportedFields.map((fieldName) =>
this.getValuesForFilters(fieldName, filters).pipe(
map((values) => ({ [fieldName]: values }))
Expand Down
Loading

0 comments on commit 3713c5d

Please sign in to comment.