<div class="btn-group w-100" ngbDropdown role="group">
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="dateBefore || dateAfter ? 'btn-primary' : 'btn-outline-primary'">
{{title}}
+ <div *ngIf="isActive" class="position-absolute top-0 start-100 p-2 translate-middle badge bg-secondary border border-light rounded-circle">
+ <span class="visually-hidden">selected</span>
+ </div>
</button>
<div class="dropdown-menu date-dropdown shadow pt-0" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
<div class="list-group list-group-flush">
- <button *ngFor="let qf of quickFilters" class="list-group-item small list-goup list-group-item-action d-flex p-2 ps-3" role="menuitem" (click)="setDateQuickFilter(qf.id)">
+ <button *ngFor="let qf of quickFilters" class="list-group-item small list-goup list-group-item-action d-flex p-2" role="menuitem" (click)="setDateQuickFilter(qf.id)">
+ <div _ngcontent-hga-c166="" class="selected-icon me-1">
+ <svg *ngIf="quickFilter === qf.id" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16">
+ <path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z"/>
+ </svg>
+ </div>
{{qf.name}}
</button>
<div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
export interface DateSelection {
before?: string
after?: string
+ dateQuery?: string
+}
+
+interface QuickFilter {
+ id: number
+ name: string
+ dateQuery: string
}
const LAST_7_DAYS = 0
this.datePlaceHolder = settings.getLocalizedDateInputFormat()
}
- quickFilters = [
- { id: LAST_7_DAYS, name: $localize`Last 7 days` },
- { id: LAST_MONTH, name: $localize`Last month` },
- { id: LAST_3_MONTHS, name: $localize`Last 3 months` },
- { id: LAST_YEAR, name: $localize`Last year` },
+ quickFilters: Array<QuickFilter> = [
+ {
+ id: LAST_7_DAYS,
+ name: $localize`Last 7 days`,
+ dateQuery: '-1 week to now',
+ },
+ {
+ id: LAST_MONTH,
+ name: $localize`Last month`,
+ dateQuery: '-1 month to now',
+ },
+ {
+ id: LAST_3_MONTHS,
+ name: $localize`Last 3 months`,
+ dateQuery: '-3 month to now',
+ },
+ { id: LAST_YEAR, name: $localize`Last year`, dateQuery: '-1 year to now' },
]
datePlaceHolder: string
@Output()
dateAfterChange = new EventEmitter<string>()
+ quickFilter: number
+
+ @Input()
+ set dateQuery(query: string) {
+ this.quickFilter = this.quickFilters.find((qf) => qf.dateQuery == query)?.id
+ }
+
+ get dateQuery(): string {
+ return (
+ this.quickFilters.find((qf) => qf.id == this.quickFilter)?.dateQuery ?? ''
+ )
+ }
+
+ @Output()
+ dateQueryChange = new EventEmitter<string>()
+
@Input()
title: string
@Output()
datesSet = new EventEmitter<DateSelection>()
+ get isActive(): boolean {
+ return (
+ this.quickFilter > -1 ||
+ this.dateAfter?.length > 0 ||
+ this.dateBefore?.length > 0
+ )
+ }
+
private datesSetDebounce$ = new Subject()
private sub: Subscription
setDateQuickFilter(qf: number) {
this.dateBefore = null
- let date = new Date()
- switch (qf) {
- case LAST_7_DAYS:
- date.setDate(date.getDate() - 7)
- break
-
- case LAST_MONTH:
- date.setMonth(date.getMonth() - 1)
- break
-
- case LAST_3_MONTHS:
- date.setMonth(date.getMonth() - 3)
- break
-
- case LAST_YEAR:
- date.setFullYear(date.getFullYear() - 1)
- break
- }
- this.dateAfter = formatDate(date, 'yyyy-MM-dd', 'en-us', 'UTC')
+ this.dateAfter = null
+ this.quickFilter = this.quickFilter == qf ? null : qf
this.onChange()
}
+ qfIsSelected(qf: number) {
+ return this.quickFilter == qf
+ }
+
onChange() {
- this.dateAfterChange.emit(this.dateAfter)
this.dateBeforeChange.emit(this.dateBefore)
- this.datesSet.emit({ after: this.dateAfter, before: this.dateBefore })
+ this.dateAfterChange.emit(this.dateAfter)
+ this.dateQueryChange.emit(this.dateQuery)
+ this.datesSet.emit({
+ after: this.dateAfter,
+ before: this.dateBefore,
+ dateQuery: this.dateQuery,
+ })
}
onChangeDebounce() {
+ this.dateQuery = null
this.datesSetDebounce$.next({
after: this.dateAfter,
before: this.dateBefore,
const TEXT_FILTER_MODIFIER_GT = 'greater'
const TEXT_FILTER_MODIFIER_LT = 'less'
+const RELATIVE_DATE_QUERY_REGEXP_CREATED = /created:\[([^\]]+)\]/g
+const RELATIVE_DATE_QUERY_REGEXP_ADDED = /added:\[([^\]]+)\]/g
+
@Component({
selector: 'app-filter-editor',
templateUrl: './filter-editor.component.html',
dateCreatedAfter: string
dateAddedBefore: string
dateAddedAfter: string
+ dateCreatedQuery: string
+ dateAddedQuery: string
_unmodifiedFilterRules: FilterRule[] = []
_filterRules: FilterRule[] = []
this.dateAddedAfter = null
this.dateCreatedBefore = null
this.dateCreatedAfter = null
+ this.dateCreatedQuery = null
+ this.dateAddedQuery = null
this.textFilterModifier = TEXT_FILTER_MODIFIER_EQUALS
value.forEach((rule) => {
this.textFilterTarget = TEXT_FILTER_TARGET_ASN
break
case FILTER_FULLTEXT_QUERY:
- this._textFilter = rule.value
+ let queryArgs = rule.value.split(',')
+ queryArgs.forEach((arg) => {
+ if (arg.match(RELATIVE_DATE_QUERY_REGEXP_CREATED)) {
+ ;[...arg.matchAll(RELATIVE_DATE_QUERY_REGEXP_CREATED)].forEach(
+ (match) => {
+ if (match[1]?.length) {
+ this.dateCreatedQuery = match[1]
+ }
+ }
+ )
+ queryArgs.splice(queryArgs.indexOf(arg), 1)
+ }
+ if (arg.match(RELATIVE_DATE_QUERY_REGEXP_ADDED)) {
+ ;[...arg.matchAll(RELATIVE_DATE_QUERY_REGEXP_ADDED)].forEach(
+ (match) => {
+ if (match[1]?.length) {
+ this.dateAddedQuery = match[1]
+ }
+ }
+ )
+ queryArgs.splice(queryArgs.indexOf(arg), 1)
+ }
+ })
+ this._textFilter = queryArgs.join(',')
this.textFilterTarget = TEXT_FILTER_TARGET_FULLTEXT_QUERY
break
case FILTER_FULLTEXT_MORELIKE:
value: this.dateAddedAfter,
})
}
+ if (this.dateAddedQuery || this.dateCreatedQuery) {
+ let queryArgs: Array<string> = []
+ if (this.dateCreatedQuery)
+ queryArgs.push(`created:[${this.dateCreatedQuery}]`)
+ if (this.dateAddedQuery) queryArgs.push(`added:[${this.dateAddedQuery}]`)
+ const existingRule = filterRules.find(
+ (fr) => fr.rule_type == FILTER_FULLTEXT_QUERY
+ )
+ if (existingRule) {
+ let existingRuleArgs = existingRule.value.split(',')
+ if (this.dateCreatedQuery) {
+ queryArgs = existingRuleArgs
+ .filter((arg) => !arg.includes('created:'))
+ .concat(queryArgs)
+ }
+ if (this.dateAddedQuery) {
+ queryArgs = existingRuleArgs
+ .filter((arg) => !arg.includes('added:'))
+ .concat(queryArgs)
+ }
+ existingRule.value = queryArgs.join(',')
+ } else {
+ filterRules.push({
+ rule_type: FILTER_FULLTEXT_QUERY,
+ value: queryArgs.join(','),
+ })
+ }
+ }
+ if (!this.dateAddedQuery && !this.dateCreatedQuery) {
+ const existingRule = filterRules.find(
+ (fr) => fr.rule_type == FILTER_FULLTEXT_QUERY
+ )
+ if (
+ existingRule?.value.includes('created:') ||
+ existingRule?.value.includes('added:')
+ ) {
+ // remove any existing date query
+ existingRule.value = existingRule.value
+ .replace(RELATIVE_DATE_QUERY_REGEXP_CREATED, '')
+ .replace(RELATIVE_DATE_QUERY_REGEXP_ADDED, '')
+ if (existingRule.value.replace(',', '').trim() === '') {
+ // if its empty now, remove it entirely
+ filterRules.splice(filterRules.indexOf(existingRule), 1)
+ }
+ }
+ }
return filterRules
}
target != TEXT_FILTER_TARGET_FULLTEXT_MORELIKE
) {
this._textFilter = ''
+ this.dateAddedQuery = ''
+ this.dateCreatedQuery = ''
}
this.textFilterTarget = target
this.textFilterInput.nativeElement.focus()