Skip to content

Commit 212cdcd

Browse files
committed
Updated page filters
1 parent 09ec2c9 commit 212cdcd

File tree

5 files changed

+106
-225
lines changed

5 files changed

+106
-225
lines changed

src/Exceptionless.Web/ClientApp/src/lib/components/filters/facets/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { FacetedFilter } from '$comp/faceted-filter';
22
import { writable } from 'svelte/store';
3-
import type { BooleanFilter, DateFilter, IFilter, NumberFilter, StringFilter, VersionFilter } from '../filters';
3+
import { type BooleanFilter, type DateFilter, type IFilter, type NumberFilter, type StringFilter, type VersionFilter } from '../filters';
44

55
import BooleanFacetedFilter from './BooleanFacetedFilter.svelte';
66
import DateFacetedFilter from './DateFacetedFilter.svelte';

src/Exceptionless.Web/ClientApp/src/lib/components/filters/filters.ts

Lines changed: 69 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { PersistentEventKnownTypes } from '$lib/models/api';
22
import type { StackStatus } from '$lib/models/api';
33
import type { Serializer } from 'svelte-persisted-store';
4+
import { get, type Writable } from 'svelte/store';
45

56
export interface IFilter {
67
readonly type: string;
@@ -428,23 +429,6 @@ export function getFilter(filter: Omit<IFilter, 'isEmpty' | 'reset' | 'toFilter'
428429
}
429430
}
430431

431-
export function defaultFilters(includeDates: boolean = true): IFilter[] {
432-
return [
433-
new OrganizationFilter(''),
434-
new ProjectFilter('', []),
435-
new StatusFilter([]),
436-
new TypeFilter([]),
437-
new DateFilter('date', 'last week'),
438-
new KeywordFilter('')
439-
].filter((f) => includeDates || f.type !== 'date');
440-
}
441-
442-
export function getFilterTypes(includeDates: boolean = true): string[] {
443-
return ['organization', 'project', 'status', 'type', 'date', 'boolean', 'number', 'reference', 'session', 'string', 'version', 'keyword'].filter(
444-
(f) => includeDates || f !== 'date'
445-
);
446-
}
447-
448432
export function setFilter(filters: IFilter[], filter: IFilter): IFilter[] {
449433
const existingFilter = filters.find((f) => f.key === filter.key && ('term' in f && 'term' in filter ? f.term === filter.term : true));
450434
if (existingFilter) {
@@ -464,21 +448,6 @@ export function setFilter(filters: IFilter[], filter: IFilter): IFilter[] {
464448
return filters;
465449
}
466450

467-
export function toggleFilter(filters: IFilter[], filter: IFilter): IFilter[] {
468-
const index = filters.findIndex((f) => f.type === filter.type && ('term' in f && 'term' in filter ? f.term === filter.term : true));
469-
if (index >= 0) {
470-
if (filters[index].toFilter() === filter.toFilter()) {
471-
filters.splice(index, 1);
472-
} else {
473-
filters[index] = filter;
474-
}
475-
} else {
476-
filters.push(filter);
477-
}
478-
479-
return filters;
480-
}
481-
482451
export class FilterSerializer implements Serializer<IFilter[]> {
483452
public parse(text: string): IFilter[] {
484453
if (!text) {
@@ -501,3 +470,71 @@ export class FilterSerializer implements Serializer<IFilter[]> {
501470
return JSON.stringify(object);
502471
}
503472
}
473+
474+
export function getDefaultFilters(includeDateFilter = true): IFilter[] {
475+
return [
476+
new OrganizationFilter(),
477+
new ProjectFilter(undefined, []),
478+
new StatusFilter([]),
479+
new TypeFilter([]),
480+
new DateFilter('date', 'last week'),
481+
new ReferenceFilter(),
482+
new SessionFilter(),
483+
new KeywordFilter()
484+
].filter((f) => includeDateFilter || f.type !== 'date');
485+
}
486+
487+
export function filterChanged(filters: Writable<IFilter[]>, updated: IFilter): void {
488+
filters.set(processFilterRules(setFilter(get(filters), updated), updated));
489+
}
490+
491+
export function filterRemoved(filters: Writable<IFilter[]>, defaultFilters: IFilter[], removed?: IFilter): void {
492+
// If detail is undefined, remove all filters.
493+
if (!removed) {
494+
filters.set(defaultFilters);
495+
} else if (defaultFilters.find((f) => f.key === removed.key)) {
496+
filters.set(processFilterRules(setFilter(get(filters), removed), removed));
497+
} else {
498+
filters.set(
499+
processFilterRules(
500+
get(filters).filter((f) => f.key !== removed.key),
501+
removed
502+
)
503+
);
504+
}
505+
}
506+
507+
export function processFilterRules(filters: IFilter[], changed?: IFilter): IFilter[] {
508+
// Allow only one filter per type and term.
509+
const groupedFilters: Partial<Record<string, IFilter[]>> = Object.groupBy(filters, (f: IFilter) => f.key);
510+
const filtered: IFilter[] = [];
511+
Object.entries(groupedFilters).forEach(([, items]) => {
512+
if (items && items.length > 0) {
513+
filtered.push(items[0]);
514+
}
515+
});
516+
517+
const projectFilter = filtered.find((f) => f.type === 'project') as ProjectFilter;
518+
if (projectFilter) {
519+
let organizationFilter = filtered.find((f) => f.type === 'organization') as OrganizationFilter;
520+
521+
// If there is a project filter, verify the organization filter is set
522+
if (!organizationFilter) {
523+
organizationFilter = new OrganizationFilter(projectFilter.organization);
524+
filtered.push(organizationFilter);
525+
}
526+
527+
// If the organization filter changes and organization is not set on the project filter, clear the project filter
528+
if (changed?.type === 'organization' && projectFilter.organization !== organizationFilter.value) {
529+
projectFilter.organization = organizationFilter.value;
530+
projectFilter.value = [];
531+
}
532+
533+
// If the project filter changes and the organization filter is not set, set it
534+
if (organizationFilter.value !== projectFilter.organization) {
535+
organizationFilter.value = projectFilter.organization;
536+
}
537+
}
538+
539+
return filtered;
540+
}

src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte

Lines changed: 4 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,7 @@
1212
import EventsDrawer from '$comp/events/EventsDrawer.svelte';
1313
import type { SummaryModel, SummaryTemplateKeys } from '$lib/models/api';
1414
15-
import {
16-
StatusFilter,
17-
TypeFilter,
18-
type IFilter,
19-
FilterSerializer,
20-
KeywordFilter,
21-
toFilter,
22-
OrganizationFilter,
23-
ProjectFilter,
24-
DateFilter,
25-
setFilter,
26-
ReferenceFilter,
27-
SessionFilter
28-
} from '$comp/filters/filters';
15+
import { type IFilter, FilterSerializer, toFilter, DateFilter, filterRemoved, filterChanged, getDefaultFilters } from '$comp/filters/filters';
2916
import CustomEventMessage from '$comp/messaging/CustomEventMessage.svelte';
3017
import { toFacetedFilters } from '$comp/filters/facets';
3118
@@ -35,16 +22,7 @@
3522
}
3623
3724
const limit = persisted<number>('events.limit', 10);
38-
const defaultFilters = [
39-
new OrganizationFilter(),
40-
new ProjectFilter(undefined, []),
41-
new StatusFilter([]),
42-
new TypeFilter([]),
43-
new DateFilter('date', 'last week'),
44-
new ReferenceFilter(),
45-
new SessionFilter(),
46-
new KeywordFilter()
47-
];
25+
const defaultFilters = getDefaultFilters();
4826
const filters = persisted<IFilter[]>('events.filters', defaultFilters, { serializer: new FilterSerializer() });
4927
$filters.push(...defaultFilters.filter((df) => !$filters.some((f) => f.key === df.key)));
5028
@@ -53,57 +31,11 @@
5331
const time = derived(filters, ($filters) => ($filters.find((f) => f.key === 'date:date') as DateFilter).value as string);
5432
5533
function onFilterChanged({ detail }: CustomEvent<IFilter>): void {
56-
filters.set(processFilterRules(setFilter($filters, detail), detail));
34+
filterChanged(filters, detail);
5735
}
5836
5937
function onFilterRemoved({ detail }: CustomEvent<IFilter | undefined>): void {
60-
// If detail is undefined, remove all filters.
61-
if (!detail) {
62-
filters.set(defaultFilters);
63-
} else if (defaultFilters.find((f) => f.key === detail.key)) {
64-
filters.set(processFilterRules(setFilter($filters, detail), detail));
65-
} else {
66-
filters.set(
67-
processFilterRules(
68-
$filters.filter((f) => f.key !== detail.key),
69-
detail
70-
)
71-
);
72-
}
73-
}
74-
75-
function processFilterRules(filters: IFilter[], changed?: IFilter): IFilter[] {
76-
// Allow only one filter per type and term.
77-
const groupedFilters: Record<string, IFilter[]> = Object.groupBy(filters, (f: IFilter) => f.key);
78-
const filtered: IFilter[] = [];
79-
Object.entries(groupedFilters).forEach(([_, items]) => {
80-
console.log(_);
81-
filtered.push(items[0]);
82-
});
83-
84-
const projectFilter = filtered.find((f) => f.type === 'project') as ProjectFilter;
85-
if (projectFilter) {
86-
let organizationFilter = filtered.find((f) => f.type === 'organization') as OrganizationFilter;
87-
88-
// If there is a project filter, verify the organization filter is set
89-
if (!organizationFilter) {
90-
organizationFilter = new OrganizationFilter(projectFilter.organization);
91-
filtered.push(organizationFilter);
92-
}
93-
94-
// If the organization filter changes and organization is not set on the project filter, clear the project filter
95-
if (changed?.type === 'organization' && projectFilter.organization !== organizationFilter.value) {
96-
projectFilter.organization = organizationFilter.value;
97-
projectFilter.value = [];
98-
}
99-
100-
// If the project filter changes and the organization filter is not set, set it
101-
if (organizationFilter.value !== projectFilter.organization) {
102-
organizationFilter.value = projectFilter.organization;
103-
}
104-
}
105-
106-
return filtered;
38+
filterRemoved(filters, defaultFilters, detail);
10739
}
10840
</script>
10941

src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte

Lines changed: 17 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,11 @@
1111
import { getEventsByStackIdQuery } from '$api/eventsApi';
1212
import EventsDataTable from '$comp/events/table/EventsDataTable.svelte';
1313
import EventsDrawer from '$comp/events/EventsDrawer.svelte';
14+
import CustomEventMessage from '$comp/messaging/CustomEventMessage.svelte';
1415
import type { SummaryModel, SummaryTemplateKeys } from '$lib/models/api';
1516
16-
import DateFacetedFilter from '$comp/filters/facets/DateFacetedFilter.svelte';
17-
import KeywordFacetedFilter from '$comp/filters/facets/KeywordFacetedFilter.svelte';
18-
import OrganizationFacetedFilter from '$comp/filters/facets/OrganizationFacetedFilter.svelte';
19-
import ProjectFacetedFilter from '$comp/filters/facets/ProjectFacetedFilter.svelte';
20-
import StatusFacetedFilter from '$comp/filters/facets/StatusFacetedFilter.svelte';
21-
import TypeFacetedFilter from '$comp/filters/facets/TypeFacetedFilter.svelte';
22-
import {
23-
StatusFilter,
24-
TypeFilter,
25-
type IFilter,
26-
FilterSerializer,
27-
KeywordFilter,
28-
toFilter,
29-
OrganizationFilter,
30-
ProjectFilter,
31-
DateFilter
32-
} from '$comp/filters/filters';
17+
import { type IFilter, FilterSerializer, toFilter, DateFilter, getDefaultFilters, filterChanged, filterRemoved } from '$comp/filters/filters';
18+
import { toFacetedFilters } from '$comp/filters/facets';
3319
3420
const selectedStackId = writable<string | null>(null);
3521
function onRowClick({ detail }: CustomEvent<SummaryModel<SummaryTemplateKeys>>) {
@@ -43,71 +29,34 @@
4329
});
4430
4531
const limit = persisted<number>('events.issues.limit', 10);
46-
const defaultFilters = [
47-
new OrganizationFilter(''),
48-
new ProjectFilter('', []),
49-
new StatusFilter([]),
50-
new TypeFilter([]),
51-
new DateFilter('date', 'last week'),
52-
new KeywordFilter('')
53-
];
32+
const defaultFilters = getDefaultFilters().filter((f) => f.key !== 'type');
5433
const filters = persisted<IFilter[]>('events.issues.filters', defaultFilters, { serializer: new FilterSerializer() });
55-
$filters.push(...defaultFilters.filter((df) => !$filters.some((f) => f.type === df.type)));
34+
$filters.push(...defaultFilters.filter((df) => !$filters.some((f) => f.key === df.key)));
5635
57-
const filter = derived(filters, ($filters) => toFilter($filters.filter((f) => f.type !== 'date')));
58-
const facets = derived(filters, ($filters) => [
59-
{
60-
title: 'Organization',
61-
component: OrganizationFacetedFilter,
62-
filter: $filters.find((f) => f.type === 'organization')!
63-
},
64-
{
65-
title: 'Project',
66-
component: ProjectFacetedFilter,
67-
filter: $filters.find((f) => f.type === 'project')!
68-
},
69-
{
70-
title: 'Status',
71-
component: StatusFacetedFilter,
72-
filter: $filters.find((f) => f.type === 'status')!
73-
},
74-
{
75-
title: 'Type',
76-
component: TypeFacetedFilter,
77-
filter: $filters.find((f) => f.type === 'type')!
78-
},
79-
{
80-
title: 'Date Range',
81-
component: DateFacetedFilter,
82-
filter: $filters.find((f) => f.type === 'date')!
83-
},
84-
{
85-
title: 'Keyword',
86-
component: KeywordFacetedFilter,
87-
filter: $filters.find((f) => f.type === 'keyword')!
88-
}
89-
]);
90-
const time = derived(filters, ($filters) => ($filters.find((f) => f.type === 'date') as DateFilter).value as string);
36+
const filter = derived(filters, ($filters) => toFilter($filters.filter((f) => f.key !== 'date:date')));
37+
const facets = derived(filters, ($filters) => toFacetedFilters($filters));
38+
const time = derived(filters, ($filters) => ($filters.find((f) => f.key === 'date:date') as DateFilter).value as string);
9139
92-
function onFiltersChanged({ detail }: CustomEvent<IFilter[]>) {
93-
const organizationFilter = detail.find((f) => f.type === 'organization') as OrganizationFilter;
94-
const projectFilter = detail.find((f) => f.type === 'project') as ProjectFilter;
95-
if (organizationFilter.value !== projectFilter.organization) {
96-
projectFilter.organization = organizationFilter.value;
97-
projectFilter.value = [];
40+
function onFilterChanged({ detail }: CustomEvent<IFilter>): void {
41+
if (detail.key !== 'type') {
42+
filterChanged(filters, detail);
9843
}
44+
}
9945
100-
filters.set(detail);
46+
function onFilterRemoved({ detail }: CustomEvent<IFilter | undefined>): void {
47+
filterRemoved(filters, defaultFilters, detail);
10148
}
10249
</script>
10350

51+
<CustomEventMessage type="filter" on:message={onFilterChanged}></CustomEventMessage>
52+
10453
<div class="flex flex-col space-y-4">
10554
<Card.Root>
10655
<Card.Title tag="h2" class="p-6 pb-4 text-2xl">Issues</Card.Title>
10756
<Card.Content>
10857
<EventsDataTable {filter} {limit} {time} on:rowclick={onRowClick} mode="stack_frequent" pageFilter="(type:404 OR type:error)">
10958
<svelte:fragment slot="toolbar">
110-
<FacetedFilter.Root {facets} on:changed={onFiltersChanged}></FacetedFilter.Root>
59+
<FacetedFilter.Root {facets} on:changed={onFilterChanged} on:remove={onFilterRemoved}></FacetedFilter.Root>
11160
</svelte:fragment>
11261
</EventsDataTable>
11362
</Card.Content>

0 commit comments

Comments
 (0)