Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
fix(quick-filter): Use internal uid for the filter generation.
Browse files Browse the repository at this point in the history
This issue should be a further step to make the quick filter stable.

Fixes #1647
Fixes #1522
  • Loading branch information
Lukas Holzer committed Sep 25, 2020
1 parent 476cce7 commit a0c75e2
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,24 @@ import { NgModule } from '@angular/core';
import { Route, RouterModule } from '@angular/router';
import { DtFilterFieldModule } from '@dynatrace/barista-components/filter-field';
import { DtE2EFilterField } from './filter-field';
import {
DtExampleFilterFieldAsync,
DtFilterFieldExamplesModule,
} from '@dynatrace/examples/filter-field';

const routes: Route[] = [{ path: '', component: DtE2EFilterField }];
const routes: Route[] = [
{ path: '', component: DtE2EFilterField },
{ path: 'async', component: DtExampleFilterFieldAsync },
];

@NgModule({
declarations: [DtE2EFilterField],
imports: [CommonModule, RouterModule.forChild(routes), DtFilterFieldModule],
imports: [
CommonModule,
DtFilterFieldExamplesModule,
RouterModule.forChild(routes),
DtFilterFieldModule,
],
exports: [],
providers: [],
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<dt-quick-filter
[dataSource]="_dataSource"
(currentFilterChanges)="currentFilterChanges($event)"
aria-label="Filter By Input value"
label="Filter by"
clearAllLabel="Clear all"
>
<dt-quick-filter-title>Quick-filter</dt-quick-filter-title>
<dt-quick-filter-sub-title>
All options in the filter field above
</dt-quick-filter-sub-title>

Quick-filter async example
</dt-quick-filter>
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* @license
* Copyright 2020 Dynatrace LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Component } from '@angular/core';
import { isObject } from '@dynatrace/barista-components/core';
import {
DtQuickFilterDefaultDataSource,
DtQuickFilterDefaultDataSourceConfig,
DtQuickFilterCurrentFilterChangeEvent,
DtQuickFilterDefaultDataSourceType,
} from '@dynatrace/barista-components/experimental/quick-filter';

const filterFieldData = {
autocomplete: [
{
name: 'AUT (async)',
async: true,
autocomplete: [],
},
{
name: 'USA',
autocomplete: [
{ name: 'San Francisco' },
{ name: 'Los Angeles' },
{ name: 'New York' },
],
},
],
};

const asyncData = {
name: 'AUT (async)',
autocomplete: [{ name: 'Linz' }, { name: 'Vienna' }, { name: 'Graz' }],
};

@Component({
selector: 'dt-e2e-quick-filter-async',
templateUrl: './quick-filter-async.html',
// template: ''
})
export class DtE2EQuickFilterAsync {
/** configuration for the quick filter */
private _config: DtQuickFilterDefaultDataSourceConfig = {
// Method to decide if a node should be displayed in the quick filter
showInSidebar: (node) =>
isObject(node) && node.name && node.name !== 'AUT (async)',
};

_dataSource = new DtQuickFilterDefaultDataSource<
DtQuickFilterDefaultDataSourceType
>(filterFieldData, this._config);

currentFilterChanges(
event: DtQuickFilterCurrentFilterChangeEvent<
DtQuickFilterDefaultDataSourceType
>,
): void {
if (event.added[0] === filterFieldData.autocomplete[0]) {
setTimeout(() => {
this._dataSource.data = asyncData;
}, 1000);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,21 @@ import { Route, RouterModule } from '@angular/router';
import { DtQuickFilterModule } from '@dynatrace/barista-components/experimental/quick-filter';
import { DtE2EQuickFilter } from './quick-filter/quick-filter';
import { DtE2EQuickFilterInitialData } from './quick-filter-initial-data/quick-filter-initial-data';
import { DtE2EQuickFilterAsync } from './quick-filter-async/quick-filter-async';

const routes: Route[] = [
{ path: '', component: DtE2EQuickFilter },
{ path: 'initial-data', component: DtE2EQuickFilterInitialData },
{ path: 'async', component: DtE2EQuickFilterAsync },
];

@NgModule({
declarations: [DtE2EQuickFilter, DtE2EQuickFilterInitialData],
imports: [CommonModule, RouterModule.forChild(routes), DtQuickFilterModule],
declarations: [
DtE2EQuickFilter,
DtE2EQuickFilterInitialData,
DtE2EQuickFilterAsync,
],
imports: [CommonModule, DtQuickFilterModule, RouterModule.forChild(routes)],
exports: [],
providers: [],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<ng-template [ngIf]="_isDistinct()" [ngIfElse]="notDistinct">
<dt-radio-group
class="dt-quick-filter-group-items"
(change)="_selectOption($event)"
(change)="_selectOption($event, _nodeDef)"
>
<dt-radio-button [checked]="_isNothingSelected()" (change)="_unsetGroup()">
Any
Expand All @@ -30,7 +30,7 @@
*ngFor="let item of _getOptions()"
[value]="item"
[checked]="_isActive(item)"
(change)="_selectCheckBox($event)"
(change)="_selectCheckBox($event, _nodeDef)"
>
{{ _getViewValue(item) }}
</dt-checkbox>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '@angular/core';
import { DtCheckboxChange } from '@dynatrace/barista-components/checkbox';
import {
DtAutocompleteValue,
DtNodeDef,
isDtOptionDef,
isDtRenderType,
Expand All @@ -51,7 +52,7 @@ import {
class: 'dt-quick-filter-group',
},
})
export class DtQuickFilterGroup {
export class DtQuickFilterGroup<T = any> {
/**
* @internal
* The aria-level of the group headlines for the document outline.
Expand All @@ -63,7 +64,7 @@ export class DtQuickFilterGroup {

/** @internal The list of all active filters */
@Input()
set activeFilters(filters: any[][]) {
set activeFilters(filters: DtAutocompleteValue<T>[][]) {
this._activeFilterPaths = buildIdPathsFromFilters(filters || []);
this._changeDetectorRef.markForCheck();
}
Expand All @@ -82,17 +83,17 @@ export class DtQuickFilterGroup {
}

/** @internal Updates a radio box */
_selectOption(change: DtRadioChange<DtNodeDef>): void {
_selectOption(change: DtRadioChange<DtNodeDef>, group: DtNodeDef): void {
if (change.value) {
this.filterChange.emit(updateFilter(change.value));
this.filterChange.emit(updateFilter([group, change.value]));
}
}

/** @internal Select or deselect a checkbox */
_selectCheckBox(change: DtCheckboxChange<DtNodeDef>): void {
_selectCheckBox(change: DtCheckboxChange<DtNodeDef>, group: DtNodeDef): void {
const action = change.checked
? addFilter(change.source.value)
: removeFilter(change.source.value);
? addFilter([group, change.source.value])
: removeFilter(change.source.value.option!.uid!);
this.filterChange.emit(action);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@
* limitations under the License.
*/

import { DELIMITER } from '@dynatrace/barista-components/filter-field';
import { DtAutocompleteValue } from '@dynatrace/barista-components/filter-field';

export function buildIdPathsFromFilters(filters: any[][]): string[] {
return filters.map((path) =>
path.reduce(
(previousValue, currentValue) =>
`${previousValue.name}${DELIMITER}${currentValue.name}${DELIMITER}`,
),
);
/** @internal Build an array of uids from the options without the groups */
export function buildIdPathsFromFilters(
filters: DtAutocompleteValue<any>[][],
): string[] {
return filters.map((group) => group[group.length - 1].option.uid || '');
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ import {
DtFilterField,
DtFilterFieldChangeEvent,
DtFilterFieldCurrentFilterChangeEvent,
isDtAutocompleteValue,
isDtOptionDef,
} from '@dynatrace/barista-components/filter-field';
import { BehaviorSubject, Subject, Observable } from 'rxjs';
import { switchMap, take, takeUntil } from 'rxjs/operators';
import { _getSourcesOfDtFilterValues } from '../../../filter-field/src/types';
import { DtQuickFilterDataSource } from './quick-filter-data-source';
import { Action, setFilters, switchDataSource } from './state/actions';
import { quickFilterReducer } from './state/reducer';
Expand Down Expand Up @@ -143,7 +146,9 @@ export class DtQuickFilter<T = any> implements AfterViewInit, OnDestroy {
return this._filterField.filters;
}
set filters(filters: T[][]) {
this._store.dispatch(setFilters(filters));
this._filterField.filters = filters;
// TODO: lukas.holzer convert them to devs
// this._store.dispatch(setFilters(filters));
}

/**
Expand All @@ -169,7 +174,7 @@ export class DtQuickFilter<T = any> implements AfterViewInit, OnDestroy {
/** Angular life-cycle hook that will be called after the view is initialized */
ngAfterViewInit(): void {
// We need to wait for the first on stable call, otherwise the
// underlying filterfield will thow an expression changed after checked
// underlying filter field will throw an expression changed after checked
// error. Deferring the first filter setting.
// Relates to a very weird and hard to reproduce bug described in
// https://github.com/dynatrace-oss/barista/issues/1305
Expand All @@ -181,9 +186,9 @@ export class DtQuickFilter<T = any> implements AfterViewInit, OnDestroy {
)
// When the filters changes apply them to the filter field
.subscribe((filters) => {
if (this._filterField.filters !== filters) {
this._filterField.filters = filters;
}
this._filterField.filters = filters.map((values) =>
_getSourcesOfDtFilterValues(values),
);
});
}

Expand Down Expand Up @@ -212,7 +217,12 @@ export class DtQuickFilter<T = any> implements AfterViewInit, OnDestroy {

/** @internal Bubble the filter field change event through */
_filterFieldChanged(change: DtFilterFieldChangeEvent<T>): void {
this._store.dispatch(setFilters(change.filters));
// Filter only autocomplete filters as we don't use free-text and range in the quick-filter
const filteredFilters = this._filterField._filterValues.filter((group) =>
group.every((value) => isDtAutocompleteValue(value)),
);

this._store.dispatch(setFilters(filteredFilters));
this.filterChanges.emit(change);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
*/
import {
DtFilterFieldDataSource,
DtFilterValue,
DtNodeDef,
} from '@dynatrace/barista-components/filter-field';

/** Enum for all the possible action types */
/** @internal Enum for all the possible action types */
export enum ActionType {
INIT = '@@actions init',
ADD_FILTER = '@@actions add filter',
Expand All @@ -30,41 +31,41 @@ export enum ActionType {
UPDATE_DATA_SOURCE = '@@actions update dataSource',
}

/** Interface for an action */
/** @internal Interface for an action */
export interface Action<T = any> {
readonly type: ActionType;
payload?: T;
}
/** Function which helps to create actions without mistakes */
/** @internal Function which helps to create actions without mistakes */
export const action = <T>(type: ActionType, payload?: T): Action<T> => ({
type,
payload,
});

/** Action that sets filters (Bulk operation for addFilter) */
export const setFilters = (filters: any[][]) =>
/** @internal Action that sets filters (Bulk operation for addFilter) */
export const setFilters = (filters: DtFilterValue[][]) =>
action<any[][]>(ActionType.SET_FILTERS, filters);

/** Action that unsets a filter group */
/** @internal Action that unsets a filter group */
export const unsetFilterGroup = (group: DtNodeDef) =>
action<DtNodeDef>(ActionType.UNSET_FILTER_GROUP, group);

/** Action that adds a filter */
export const addFilter = (item: DtNodeDef) =>
action<DtNodeDef>(ActionType.ADD_FILTER, item);
/** @internal Action that adds a filter */
export const addFilter = (filter: DtNodeDef[]) =>
action<DtNodeDef[]>(ActionType.ADD_FILTER, filter);

/** Action that removes a filter */
export const removeFilter = (item: DtNodeDef) =>
action<DtNodeDef>(ActionType.REMOVE_FILTER, item);
/** @internal Action that removes a filter */
export const removeFilter = (uid: string) =>
action<string>(ActionType.REMOVE_FILTER, uid);

/** Action that updates a filter */
export const updateFilter = (item: DtNodeDef) =>
action<DtNodeDef>(ActionType.UPDATE_FILTER, item);
/** @internal Action that updates a filter */
export const updateFilter = (filter: DtNodeDef[]) =>
action<DtNodeDef[]>(ActionType.UPDATE_FILTER, filter);

/** Action that subscribes to a new data source */
/** @internal Action that subscribes to a new data source */
export const switchDataSource = (item: DtFilterFieldDataSource<any>) =>
action<DtFilterFieldDataSource<any>>(ActionType.SWITCH_DATA_SOURCE, item);

/** Action that updates the data source */
/** @internal Action that updates the data source */
export const updateDataSource = (nodeDef: DtNodeDef) =>
action<DtNodeDef>(ActionType.UPDATE_DATA_SOURCE, nodeDef);
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,26 @@ import {
DtNodeDef,
} from '@dynatrace/barista-components/filter-field';
import { MonoTypeOperatorFunction, Observable } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators';
import { filter, map, switchMap, take } from 'rxjs/operators';
import { Action, ActionType, updateDataSource } from './actions';
import { QuickFilterState } from './store';

/** Type for an effect */
/** @internal Type for an effect */
export type Effect = (
action$: Observable<Action>,
state$?: Observable<QuickFilterState>,
) => Observable<Action>;

/** Operator to filter actions */
/** @internal Operator to filter actions */
export const ofType = <T>(
...types: ActionType[]
): MonoTypeOperatorFunction<Action<T>> =>
filter((action: Action) => types.indexOf(action.type) > -1);

/** Connects to a new Data dataSource */
/** @internal Connects to a new Data dataSource */
export const switchDataSourceEffect: Effect = (action$: Observable<Action>) =>
action$.pipe(
ofType<DtFilterFieldDataSource<any>>(ActionType.SWITCH_DATA_SOURCE),
switchMap((action) => action.payload!.connect()),
switchMap((action) => action.payload!.connect().pipe(take(1))),
map((nodeDef: DtNodeDef) => updateDataSource(nodeDef)),
);
Loading

0 comments on commit a0c75e2

Please sign in to comment.