diff --git a/.vscode/settings.json b/.vscode/settings.json
index 561c8e081c..3a5d8abc49 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -2,5 +2,6 @@
"editor.tabSize": 2,
"files.eol": "\n",
"editor.defaultFormatter": "esbenp.prettier-vscode",
- "typescript.tsdk": "node_modules/typescript/lib"
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "explorer.compactFolders": false
}
diff --git a/README.md b/README.md
index 3a7c783099..b50e902610 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
[Barista](https://barista.dynatrace.com) is the name of
[Dynatrace](https://www.dynatrace.com)'s design system, that contains all
ingredients to create extraordinary user experiences. Like a barista picks the
-best ingredients for their coffee – and we love coffee!
+best ingredients for their coffee – and we love coffee!
One major part of the Barista design system is our
[components library](https://github.com/dynatrace-oss/barista/tree/master/components)
diff --git a/angular.json b/angular.json
index f1622ff148..63f02a50b4 100644
--- a/angular.json
+++ b/angular.json
@@ -2522,6 +2522,46 @@
},
"schematics": {}
},
+ "quick-filter": {
+ "projectType": "library",
+ "root": "libs/barista-components/experimental/quick-filter",
+ "sourceRoot": "libs/barista-components/experimental/quick-filter/src",
+ "prefix": "dt",
+ "architect": {
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "libs/barista-components/experimental/quick-filter/tsconfig.lib.json",
+ "libs/barista-components/experimental/quick-filter/tsconfig.spec.json"
+ ],
+ "exclude": [
+ "**/node_modules/**",
+ "!libs/barista-components/experimental/quick-filter/**"
+ ]
+ }
+ },
+ "lint-styles": {
+ "builder": "@dynatrace/barista-builders:stylelint",
+ "options": {
+ "stylelintConfig": ".stylelintrc",
+ "reportFile": "dist/stylelint/report.xml",
+ "exclude": ["**/node_modules/**"],
+ "files": [
+ "libs/barista-components/experimental/quick-filter/**/*.scss"
+ ]
+ }
+ },
+ "test": {
+ "builder": "@nrwl/jest:jest",
+ "options": {
+ "jestConfig": "libs/barista-components/experimental/quick-filter/jest.config.js",
+ "tsConfig": "libs/barista-components/experimental/quick-filter/tsconfig.spec.json",
+ "setupFile": "libs/barista-components/experimental/quick-filter/src/test-setup.ts"
+ }
+ }
+ }
+ },
"radial-chart": {
"projectType": "library",
"root": "libs/barista-components/radial-chart",
diff --git a/apps/components-e2e/src/app/app.routing.module.ts b/apps/components-e2e/src/app/app.routing.module.ts
index c07b84634f..e4ffb37bef 100644
--- a/apps/components-e2e/src/app/app.routing.module.ts
+++ b/apps/components-e2e/src/app/app.routing.module.ts
@@ -138,6 +138,13 @@ export const routes: Routes = [
module => module.DtE2EProgressBarModule,
),
},
+ {
+ path: 'quick-filter',
+ loadChildren: () =>
+ import('../components/quick-filter/quick-filter.module').then(
+ module => module.DtE2EQuickFilterModule,
+ ),
+ },
{
path: 'radial-chart',
loadChildren: () =>
diff --git a/apps/components-e2e/src/components/filter-field/filter-field.e2e.ts b/apps/components-e2e/src/components/filter-field/filter-field.e2e.ts
index 92c799dd15..8e837d1fff 100644
--- a/apps/components-e2e/src/components/filter-field/filter-field.e2e.ts
+++ b/apps/components-e2e/src/components/filter-field/filter-field.e2e.ts
@@ -47,7 +47,7 @@ test('should not show a error box if there is no validator provided', async () =
test('should show a error box if does not meet the validation function', async () => {
await clickOption(3)
- .typeText(input, 'a')
+ .typeText(input, 'a', { speed: 0.1 })
// Wait for the filter field to refresh the error message.
.wait(250)
.expect(errorBox.exists)
@@ -56,12 +56,10 @@ test('should show a error box if does not meet the validation function', async (
.match(/min 3 characters/gm);
});
-// TODO: lukas.holzer investigate why this test is flaky on Browserstack
-// tslint:disable-next-line: dt-no-focused-tests
-test.skip('should show is required error when the input is dirty', async () => {
+test('should show is required error when the input is dirty', async () => {
await clickOption(3)
- .typeText(input, 'a')
- .pressKey('backspace')
+ .typeText(input, 'a', { speed: 0.1 })
+ .pressKey('backspace', { speed: 0.1 })
.expect(errorBox.exists)
.ok()
.expect(errorBox.innerText)
@@ -173,7 +171,7 @@ test('should choose a freetext node with the mouse and submit the correct value
// Select the free text node and start typing
await clickOption(4)
- // Wait for a certain amout fo time to let the filterfield refresh
+ // Wait for a certain amount fo time to let the filterfield refresh
.wait(250)
// Send the correct value into the input field
.typeText(input, 'Custom selection');
@@ -190,7 +188,7 @@ test('should choose a freetext node with the mouse and submit the correct value
.expect(tags.length)
.eql(1)
.expect(tags[0])
- .eql('Autocomplete with free text optionsCustom selection');
+ .match(/Autocomplete with free text options/);
});
test('should choose a freetext node with the mouse and submit an empty value immediately', async (testController: TestController) => {
diff --git a/apps/components-e2e/src/components/filter-field/filter-field.po.ts b/apps/components-e2e/src/components/filter-field/filter-field.po.ts
index fb639ad4f8..dacc2de8ca 100644
--- a/apps/components-e2e/src/components/filter-field/filter-field.po.ts
+++ b/apps/components-e2e/src/components/filter-field/filter-field.po.ts
@@ -23,6 +23,12 @@ export const clearAll = Selector('.dt-filter-field-clear-all-button');
export const filterTags = Selector('dt-filter-field-tag');
export const tagOverlay = Selector('.dt-overlay-container');
+/** Selector for the delete button (x) on a filter with a specific text */
+export const tagDeleteButton = (filterText: string) =>
+ Selector('dt-filter-field-tag')
+ .withText(filterText)
+ .child('.dt-filter-field-tag-button');
+
export const input = Selector('input');
export const switchToFirstDatasource = Selector('#switchToFirstDatasource');
@@ -36,9 +42,8 @@ export function clickOption(
const controller = testController || t;
return controller
- .click(filterField, { speed: 0.2 })
- .wait(250)
- .click(option(nth), { speed: 0.2 });
+ .click(filterField, { speed: 0.4 })
+ .click(option(nth), { speed: 0.4 });
}
/** Focus the input of the filter field to send key events to it. */
@@ -46,7 +51,7 @@ export const focusFilterFieldInput = ClientFunction(() => {
(document.querySelector('#filter-field input') as HTMLElement).focus();
});
-/** Retreive all set tags in the filter field and their values. */
+/** Retrieve all set tags in the filter field and their values. */
export const getFilterfieldTags = ClientFunction(() => {
const filterFieldTags: HTMLElement[] = [].slice.call(
document.querySelectorAll('.dt-filter-field-tag'),
diff --git a/apps/components-e2e/src/components/filter-field/filter-field.ts b/apps/components-e2e/src/components/filter-field/filter-field.ts
index c9d4020fe5..2f16e43335 100644
--- a/apps/components-e2e/src/components/filter-field/filter-field.ts
+++ b/apps/components-e2e/src/components/filter-field/filter-field.ts
@@ -18,98 +18,24 @@
// tslint:disable no-any max-file-line-count no-unbound-method use-component-selector
import {
- Component,
- ViewChild,
ChangeDetectorRef,
+ Component,
OnDestroy,
+ ViewChild,
} from '@angular/core';
-import { Validators } from '@angular/forms';
-
import {
+ DtFilterField,
DtFilterFieldDefaultDataSource,
DtFilterFieldDefaultDataSourceType,
- DtFilterField,
} from '@dynatrace/barista-components/filter-field';
-import { takeUntil } from 'rxjs/operators';
+import {
+ FILTER_FIELD_TEST_DATA,
+ FILTER_FIELD_TEST_DATA_VALIDATORS,
+} from '@dynatrace/testing/fixtures';
import { Subject } from 'rxjs';
+import { takeUntil } from 'rxjs/operators';
-const TEST_DATA = {
- autocomplete: [
- {
- name: 'custom normal',
- suggestions: [],
- },
- {
- name: 'custom required',
- suggestions: [],
- validators: [
- { validatorFn: Validators.required, error: 'field is required' },
- ],
- },
- {
- name: 'custom with multiple',
- suggestions: [],
- validators: [
- { validatorFn: Validators.required, error: 'field is required' },
- { validatorFn: Validators.minLength(3), error: 'min 3 characters' },
- ],
- },
- {
- name: 'outer-option',
- autocomplete: [
- {
- name: 'inner-option',
- },
- ],
- },
- {
- name: 'Autocomplete with free text options',
- autocomplete: [
- { name: 'Autocomplete option 1' },
- { name: 'Autocomplete option 2' },
- { name: 'Autocomplete option 3' },
- {
- name: 'Autocomplete free text',
- suggestions: ['Suggestion 1', 'Suggestion 2', 'Suggestion 3'],
- validators: [],
- },
- ],
- },
- ],
-};
-
-const TEST_DATA_2 = {
- autocomplete: [
- {
- name: 'AUT',
- distinct: true,
- autocomplete: [{ name: 'Linz' }, { name: 'Vienna' }, { name: 'Graz' }],
- },
- {
- name: 'USA',
- autocomplete: [
- { name: 'San Francisco' },
- { name: 'Los Angeles' },
- { name: 'New York' },
- { name: 'Custom', suggestions: [] },
- ],
- },
- {
- name: 'Requests per minute',
- range: {
- operators: {
- range: true,
- equal: true,
- greaterThanEqual: true,
- lessThanEqual: true,
- },
- unit: 's',
- },
- },
- ],
-};
-
-const DATA = [TEST_DATA, TEST_DATA_2];
+const DATA = [FILTER_FIELD_TEST_DATA_VALIDATORS, FILTER_FIELD_TEST_DATA];
@Component({
selector: 'dt-e2e-filter-field',
diff --git a/apps/components-e2e/src/components/quick-filter/quick-filter-initial-data/quick-filter-initial-data.html b/apps/components-e2e/src/components/quick-filter/quick-filter-initial-data/quick-filter-initial-data.html
new file mode 100644
index 0000000000..64d492a50e
--- /dev/null
+++ b/apps/components-e2e/src/components/quick-filter/quick-filter-initial-data/quick-filter-initial-data.html
@@ -0,0 +1,8 @@
+
+ Quick-filter
+
+ All options in the filter field above
+
+
+ my content
+
diff --git a/apps/components-e2e/src/components/quick-filter/quick-filter-initial-data/quick-filter-initial-data.ts b/apps/components-e2e/src/components/quick-filter/quick-filter-initial-data/quick-filter-initial-data.ts
new file mode 100644
index 0000000000..933af38a59
--- /dev/null
+++ b/apps/components-e2e/src/components/quick-filter/quick-filter-initial-data/quick-filter-initial-data.ts
@@ -0,0 +1,44 @@
+/**
+ * @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 { DtQuickFilterDefaultDataSource } from '@dynatrace/barista-components/experimental/quick-filter';
+import { FILTER_FIELD_TEST_DATA } from '@dynatrace/testing/fixtures';
+
+@Component({
+ selector: 'dt-e2e-quick-filter',
+ templateUrl: 'quick-filter-initial-data.html',
+})
+export class DtE2EQuickFilterInitialData {
+ _dataSource = new DtQuickFilterDefaultDataSource(FILTER_FIELD_TEST_DATA, {
+ showInSidebar: () => true,
+ });
+
+ _initialFilters = [
+ [
+ FILTER_FIELD_TEST_DATA.autocomplete[0],
+ FILTER_FIELD_TEST_DATA.autocomplete[0].autocomplete![1],
+ ],
+ [
+ FILTER_FIELD_TEST_DATA.autocomplete[1],
+ FILTER_FIELD_TEST_DATA.autocomplete[1].autocomplete![2],
+ ],
+ ];
+
+ constructor() {
+ console.log(this._initialFilters);
+ }
+}
diff --git a/apps/components-e2e/src/components/quick-filter/quick-filter.e2e.ts b/apps/components-e2e/src/components/quick-filter/quick-filter.e2e.ts
new file mode 100644
index 0000000000..d533744f6a
--- /dev/null
+++ b/apps/components-e2e/src/components/quick-filter/quick-filter.e2e.ts
@@ -0,0 +1,157 @@
+/**
+ * @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 { waitForAngular } from '../../utils/wait-for-angular';
+import {
+ clearAll,
+ getFilterfieldTags,
+ tagDeleteButton,
+} from '../filter-field/filter-field.po';
+import {
+ getGroupItem,
+ getGroupItemInput,
+ getSelectedItem,
+} from './quick-filter.po';
+
+fixture('Quick Filter')
+ .page('http://localhost:4200/quick-filter')
+ .meta({
+ 'filter-field': true,
+ 'quick-filter': true,
+ drawer: true,
+ checkbox: true,
+ radio: true,
+ })
+ .beforeEach(async (testController: TestController) => {
+ await testController.resizeWindow(1200, 800);
+ await waitForAngular();
+ });
+
+test('if nothing is selected the distinct should be set to all', async (testController: TestController) => {
+ await testController
+ .expect(getSelectedItem('AUT').textContent)
+ .match(/Any/)
+ .expect(getSelectedItem('USA').exists)
+ .notOk();
+});
+
+test('if nothing is selected the filter friedl should be empty', async (testController: TestController) => {
+ await testController.expect(getFilterfieldTags()).eql([]);
+});
+
+test('if distinct option gets updated it should update the filter field', async (testController: TestController) => {
+ await testController
+ .expect(getFilterfieldTags())
+ .eql([])
+ .click(getGroupItem('AUT', 'Linz'), { speed: 0.3 })
+ .expect(getFilterfieldTags())
+ .eql(['AUTLinz'])
+ .expect(getSelectedItem('AUT').textContent)
+ .match(/Linz/)
+ .click(getGroupItem('AUT', 'Graz'), { speed: 0.3 })
+ .expect(getFilterfieldTags())
+ .eql(['AUTGraz'])
+ .expect(getSelectedItem('AUT').textContent)
+ .match(/Graz/);
+});
+
+test('if it is possible to select multiple options', async (testController: TestController) => {
+ await testController
+ .click(getGroupItem('USA', 'San Francisco'), { speed: 0.4 })
+ .expect(getFilterfieldTags())
+ .eql(['USASan Francisco'])
+ .click(getGroupItem('USA', 'Los Angeles'), { speed: 0.4 })
+ .expect(getFilterfieldTags())
+ .eql(['USASan Francisco', 'USALos Angeles'])
+ .click(getGroupItem('USA', 'New York'), { speed: 0.4 })
+ .expect(getFilterfieldTags())
+ .eql(['USASan Francisco', 'USALos Angeles', 'USANew York']);
+});
+
+test('if it is possible to select and deselect multiple options', async (testController: TestController) => {
+ await testController
+ .click(getGroupItem('USA', 'San Francisco'))
+ .click(getGroupItem('USA', 'Los Angeles'))
+ .click(getGroupItem('USA', 'New York'), { speed: 0.4 })
+ .expect(getFilterfieldTags())
+ .eql(['USASan Francisco', 'USALos Angeles', 'USANew York'])
+ .click(getGroupItem('USA', 'San Francisco'), { speed: 0.4 })
+ .expect(getFilterfieldTags())
+ .eql(['USALos Angeles', 'USANew York']);
+});
+
+test('if it is possible to reset all filters via the filter fields clearAll button', async (testController: TestController) => {
+ await testController
+ .click(getGroupItem('USA', 'San Francisco'))
+ .click(getGroupItem('USA', 'Los Angeles'))
+ .click(getGroupItem('USA', 'New York'), { speed: 0.4 })
+ .expect(getFilterfieldTags())
+ .eql(['USASan Francisco', 'USALos Angeles', 'USANew York'])
+ .click(clearAll, { speed: 0.4 })
+ .expect(getFilterfieldTags())
+ .eql([])
+ .expect(getSelectedItem('USA').exists)
+ .notOk();
+});
+
+test('if it is possible to delete an option via the filter field', async (testController: TestController) => {
+ await testController
+ .click(getGroupItem('USA', 'San Francisco'))
+ .click(getGroupItem('USA', 'Los Angeles'))
+ .click(getGroupItem('USA', 'New York'), { speed: 0.4 })
+ .click(tagDeleteButton('Los Angeles'), { speed: 0.4 })
+ .expect(getFilterfieldTags())
+ .eql(['USASan Francisco', 'USANew York'])
+ .expect(getGroupItemInput('USA', 'New York').checked)
+ .ok()
+ .expect(getGroupItemInput('USA', 'Los Angeles').checked)
+ .notOk();
+});
+
+fixture('Quick Filter with initial Data')
+ .page('http://localhost:4200/quick-filter/initial-data')
+ .beforeEach(async (testController: TestController) => {
+ await testController.resizeWindow(1200, 800);
+ await waitForAngular();
+ });
+
+test('if the initial filter in the filter field reflects the quick filter state', async (testController: TestController) => {
+ await testController
+ .expect(getSelectedItem('AUT').textContent)
+ .match(/Vienna/)
+ .expect(getSelectedItem('USA').textContent)
+ .match(/New York/);
+});
+
+test('if the initial filters are reflected in the filter field ', async (testController: TestController) => {
+ await testController
+ .expect(getFilterfieldTags())
+ .eql(['AUTVienna', 'USANew York']);
+});
+
+test('if a distinct group get set to any remove the group from the filter', async (testController: TestController) => {
+ await testController
+ .expect(getFilterfieldTags())
+ .eql(['AUTVienna', 'USANew York'])
+ .click(getGroupItem('AUT', 'Any'), { speed: 0.3 })
+ .expect(getFilterfieldTags())
+ .eql(['USANew York'])
+ .click(getGroupItem('AUT', 'Graz'), { speed: 0.3 })
+ .expect(getFilterfieldTags())
+ .eql(['USANew York', 'AUTGraz'])
+ .expect(getSelectedItem('AUT').textContent)
+ .match(/Graz/);
+});
diff --git a/apps/components-e2e/src/components/quick-filter/quick-filter.module.ts b/apps/components-e2e/src/components/quick-filter/quick-filter.module.ts
new file mode 100644
index 0000000000..9949a31d66
--- /dev/null
+++ b/apps/components-e2e/src/components/quick-filter/quick-filter.module.ts
@@ -0,0 +1,35 @@
+/**
+ * @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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+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';
+
+const routes: Route[] = [
+ { path: '', component: DtE2EQuickFilter },
+ { path: 'initial-data', component: DtE2EQuickFilterInitialData },
+];
+
+@NgModule({
+ declarations: [DtE2EQuickFilter, DtE2EQuickFilterInitialData],
+ imports: [CommonModule, RouterModule.forChild(routes), DtQuickFilterModule],
+ exports: [],
+ providers: [],
+})
+export class DtE2EQuickFilterModule {}
diff --git a/apps/components-e2e/src/components/quick-filter/quick-filter.po.ts b/apps/components-e2e/src/components/quick-filter/quick-filter.po.ts
new file mode 100644
index 0000000000..915bbc6f65
--- /dev/null
+++ b/apps/components-e2e/src/components/quick-filter/quick-filter.po.ts
@@ -0,0 +1,44 @@
+/**
+ * @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 { Selector } from 'testcafe';
+
+export const quickFilterCloseButton = Selector('.dt-quick-filter-close');
+export const quickFilterOpenButton = Selector('.dt-quick-filter-open');
+export const quickFilterGroup = Selector('.dt-quick-filter-group');
+
+/** get a group by its name */
+export const getGroup = (group: string) =>
+ quickFilterGroup
+ .child('.dt-quick-filter-group-headline')
+ .withText(group)
+ .sibling();
+
+/** get a group item by its group and its name */
+export const getGroupItem = (group: string, item: string) =>
+ getGroup(group)
+ .child('.dt-quick-filter-group-items > *')
+ .withText(item);
+
+/** get the native input of the specified group item */
+export const getGroupItemInput = (group: string, item: string) =>
+ getGroupItem(group, item)
+ .child('label')
+ .child('input');
+
+/** get the selected item of the group */
+export const getSelectedItem = (groupText: string) =>
+ getGroup(groupText).child('.dt-checkbox-checked, .dt-radio-checked');
diff --git a/apps/components-e2e/src/components/quick-filter/quick-filter/quick-filter.html b/apps/components-e2e/src/components/quick-filter/quick-filter/quick-filter.html
new file mode 100644
index 0000000000..b57249fce3
--- /dev/null
+++ b/apps/components-e2e/src/components/quick-filter/quick-filter/quick-filter.html
@@ -0,0 +1,20 @@
+
+ Quick-filter
+
+ All options in the filter field above
+
+
+ my content
+
+
+
+
diff --git a/apps/components-e2e/src/components/quick-filter/quick-filter/quick-filter.ts b/apps/components-e2e/src/components/quick-filter/quick-filter/quick-filter.ts
new file mode 100644
index 0000000000..4c1f2be671
--- /dev/null
+++ b/apps/components-e2e/src/components/quick-filter/quick-filter/quick-filter.ts
@@ -0,0 +1,50 @@
+/**
+ * @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 {
+ DtQuickFilterDefaultDataSource,
+ DtQuickFilterDefaultDataSourceConfig,
+ DtQuickFilterChangeEvent,
+} from '@dynatrace/barista-components/experimental/quick-filter';
+import {
+ FILTER_FIELD_TEST_DATA,
+ FILTER_FIELD_TEST_DATA_VALIDATORS,
+} from '@dynatrace/testing/fixtures';
+import { isObject } from 'util';
+
+const DATA = [FILTER_FIELD_TEST_DATA_VALIDATORS, FILTER_FIELD_TEST_DATA];
+
+const config: DtQuickFilterDefaultDataSourceConfig = {
+ showInSidebar: node =>
+ isObject(node) && node.name && node.name !== 'Not in Quickfilter',
+};
+
+@Component({
+ selector: 'dt-e2e-quick-filter',
+ templateUrl: 'quick-filter.html',
+})
+export class DtE2EQuickFilter {
+ _dataSource = new DtQuickFilterDefaultDataSource(DATA[1], config);
+
+ filterChanges(filterEvent: DtQuickFilterChangeEvent): void {
+ console.log(filterEvent);
+ }
+
+ switchToDataSource(index: number): void {
+ this._dataSource = new DtQuickFilterDefaultDataSource(DATA[index], config);
+ }
+}
diff --git a/apps/components-e2e/src/components/quick-filter/util.ts b/apps/components-e2e/src/components/quick-filter/util.ts
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/apps/universal/src/app/barista.module.ts b/apps/universal/src/app/barista.module.ts
index 51be78c68c..dc937b56f3 100644
--- a/apps/universal/src/app/barista.module.ts
+++ b/apps/universal/src/app/barista.module.ts
@@ -14,10 +14,12 @@
* limitations under the License.
*/
+import { HttpClientModule } from '@angular/common/http';
+import { NgModule } from '@angular/core';
import { DtAlertModule } from '@dynatrace/barista-components/alert';
import { DtAutocompleteModule } from '@dynatrace/barista-components/autocomplete';
-import { DtButtonGroupModule } from '@dynatrace/barista-components/button-group';
import { DtButtonModule } from '@dynatrace/barista-components/button';
+import { DtButtonGroupModule } from '@dynatrace/barista-components/button-group';
import { DtCardModule } from '@dynatrace/barista-components/card';
import { DtCheckboxModule } from '@dynatrace/barista-components/checkbox';
import { DtConsumptionModule } from '@dynatrace/barista-components/consumption';
@@ -28,6 +30,7 @@ import { DtDrawerModule } from '@dynatrace/barista-components/drawer';
import { DtEmptyStateModule } from '@dynatrace/barista-components/empty-state';
import { DtEventChartModule } from '@dynatrace/barista-components/event-chart';
import { DtExpandableTextModule } from '@dynatrace/barista-components/expandable-text';
+import { DtQuickFilterModule } from '@dynatrace/barista-components/experimental/quick-filter';
import { DtFilterFieldModule } from '@dynatrace/barista-components/filter-field';
import { DtHighlightModule } from '@dynatrace/barista-components/highlight';
import { DtIconModule } from '@dynatrace/barista-components/icon';
@@ -52,8 +55,6 @@ import { DtTimelineChartModule } from '@dynatrace/barista-components/timeline-ch
import { DtToggleButtonGroupModule } from '@dynatrace/barista-components/toggle-button-group';
import { DtTopBarNavigationModule } from '@dynatrace/barista-components/top-bar-navigation';
import { DtTreeTableModule } from '@dynatrace/barista-components/tree-table';
-import { HttpClientModule } from '@angular/common/http';
-import { NgModule } from '@angular/core';
@NgModule({
imports: [
@@ -63,41 +64,42 @@ import { NgModule } from '@angular/core';
exports: [
DtAlertModule,
DtAutocompleteModule,
+ DtButtonGroupModule,
DtButtonModule,
- DtCheckboxModule,
- DtTableModule,
- DtLoadingDistractorModule,
- DtTileModule,
- DtTagModule,
DtCardModule,
+ DtCheckboxModule,
+ DtConsumptionModule,
+ DtContainerBreakpointObserverModule,
DtContextDialogModule,
- DtDrawerModule,
DtCopyToClipboardModule,
- DtButtonGroupModule,
- DtRadioModule,
- DtShowMoreModule,
- DtProgressCircleModule,
+ DtDrawerModule,
+ DtEmptyStateModule,
+ DtEventChartModule,
+ DtExpandableTextModule,
+ DtFilterFieldModule,
+ DtHighlightModule,
+ DtInfoGroupModule,
+ DtInputModule,
+ DtLoadingDistractorModule,
+ DtMenuModule,
+ DtOverlayModule,
DtPaginationModule,
- DtSwitchModule,
DtProgressBarModule,
+ DtProgressCircleModule,
+ DtQuickFilterModule,
+ DtRadialChartModule,
+ DtRadioModule,
DtSelectModule,
- DtInputModule,
- DtOverlayModule,
- DtTreeTableModule,
- DtToggleButtonGroupModule,
- DtInfoGroupModule,
- DtHighlightModule,
- DtConsumptionModule,
- DtFilterFieldModule,
- DtMenuModule,
- DtEmptyStateModule,
+ DtShowMoreModule,
+ DtStepperModule,
+ DtSwitchModule,
+ DtTableModule,
+ DtTagModule,
+ DtTileModule,
DtTimelineChartModule,
- DtExpandableTextModule,
- DtEventChartModule,
+ DtToggleButtonGroupModule,
DtTopBarNavigationModule,
- DtStepperModule,
- DtContainerBreakpointObserverModule,
- DtRadialChartModule,
+ DtTreeTableModule,
],
})
export class BaristaModule {}
diff --git a/apps/universal/src/app/kitchen-sink/kitchen-sink.html b/apps/universal/src/app/kitchen-sink/kitchen-sink.html
index 66a99f3794..5af6f8c9cb 100644
--- a/apps/universal/src/app/kitchen-sink/kitchen-sink.html
+++ b/apps/universal/src/app/kitchen-sink/kitchen-sink.html
@@ -355,3 +355,12 @@
Packets
+
+
+ Quick-filter
+
+ All options in the filter field above
+
+
+ my content
+
diff --git a/apps/universal/src/app/kitchen-sink/kitchen-sink.ts b/apps/universal/src/app/kitchen-sink/kitchen-sink.ts
index 42c38bdc25..7cfafae252 100644
--- a/apps/universal/src/app/kitchen-sink/kitchen-sink.ts
+++ b/apps/universal/src/app/kitchen-sink/kitchen-sink.ts
@@ -14,117 +14,19 @@
* limitations under the License.
*/
+import { Component } from '@angular/core';
import {
DtTreeControl,
DtTreeDataSource,
DtTreeFlattener,
} from '@dynatrace/barista-components/core';
-
-import { Component } from '@angular/core';
-import { DtIconType } from '@dynatrace/barista-icons';
-
-const TESTDATA: ThreadNode[] = [
- {
- name: 'hz.hzInstance_1_cluster.thread',
- icon: 'airplane',
- threadlevel: 'S0',
- totalTimeConsumption: 150,
- waiting: 123,
- running: 20,
- blocked: 0,
- children: [
- {
- name:
- 'hz.hzInstance_1_cluster.thread_1_hz.hzInstance_1_cluster.thread-1',
- icon: 'airplane',
- threadlevel: 'S1',
- totalTimeConsumption: 150,
- waiting: 123,
- running: 20,
- blocked: 0,
- },
- {
- name: 'hz.hzInstance_1_cluster.thread-2',
- icon: 'airplane',
- threadlevel: 'S1',
- totalTimeConsumption: 150,
- waiting: 130,
- running: 0,
- blocked: 0,
- },
- ],
- },
- {
- name: 'jetty',
- icon: 'airplane',
- threadlevel: 'S0',
- totalTimeConsumption: 150,
- waiting: 123,
- running: 20,
- blocked: 0,
- children: [
- {
- name: 'jetty-422',
- icon: 'airplane',
- threadlevel: 'S1',
- totalTimeConsumption: 150,
- waiting: 123,
- running: 20,
- blocked: 0,
- },
- {
- name: 'jetty-423',
- icon: 'airplane',
- threadlevel: 'S1',
- totalTimeConsumption: 150,
- waiting: 130,
- running: 0,
- blocked: 0,
- },
- {
- name: 'jetty-424',
- icon: 'airplane',
- threadlevel: 'S1',
- totalTimeConsumption: 150,
- waiting: 130,
- running: 0,
- blocked: 0,
- },
- ],
- },
- {
- name: 'Downtime timer',
- icon: 'airplane',
- threadlevel: 'S0',
- totalTimeConsumption: 150,
- waiting: 123,
- running: 20,
- blocked: 0,
- },
-];
-
-export class ThreadNode {
- name: string;
- threadlevel: string;
- totalTimeConsumption: number;
- blocked: number;
- running: number;
- waiting: number;
- icon: DtIconType;
- children?: ThreadNode[];
-}
-
-export class ThreadFlatNode {
- name: string;
- threadlevel: string;
- totalTimeConsumption: number;
- blocked: number;
- running: number;
- waiting: number;
- icon: DtIconType;
- level: number;
- expandable: boolean;
-}
+import { DtQuickFilterDefaultDataSource } from '@dynatrace/barista-components/experimental/quick-filter';
+import {
+ FILTER_FIELD_TEST_DATA,
+ ThreadFlatNode,
+ ThreadNode,
+ TREE_TABLE_TEST_DATA,
+} from '@dynatrace/testing/fixtures';
@Component({
selector: 'dt-kitchen-sink',
@@ -141,6 +43,13 @@ export class KitchenSink {
treeFlattener: DtTreeFlattener;
treeTableDataSource: DtTreeDataSource;
+ quickFilterDataSource = new DtQuickFilterDefaultDataSource(
+ FILTER_FIELD_TEST_DATA,
+ {
+ showInSidebar: () => true,
+ },
+ );
+
constructor() {
this.treeControl = new DtTreeControl(
this._getLevel,
@@ -156,7 +65,7 @@ export class KitchenSink {
this.treeControl,
this.treeFlattener,
);
- this.treeTableDataSource.data = TESTDATA;
+ this.treeTableDataSource.data = TREE_TABLE_TEST_DATA;
}
hasChild = (_: number, _nodeData: ThreadFlatNode) => _nodeData.expandable;
diff --git a/libs/barista-components/drawer/src/drawer-container.scss b/libs/barista-components/drawer/src/drawer-container.scss
index 38b5d31bce..4539c4ae82 100644
--- a/libs/barista-components/drawer/src/drawer-container.scss
+++ b/libs/barista-components/drawer/src/drawer-container.scss
@@ -40,7 +40,6 @@ $dt-backdrop-opacity: 0.4;
flex-grow: 1;
flex-shrink: 1;
display: block;
- height: 100%;
overflow: auto;
will-change: contents;
diff --git a/libs/barista-components/experimental/quick-filter/README.md b/libs/barista-components/experimental/quick-filter/README.md
new file mode 100644
index 0000000000..85a632d8b8
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/README.md
@@ -0,0 +1,49 @@
+# Quick Filter (experimental)
+
+Note: This component is still experimental, use with caution! Help us get this
+component out of the experimental state by providing feedback.
+
+
+
+
+
+## Imports
+
+You have to import the `DtQuickFilterModule` when you want to use the
+``, `` and
+``. Note that you need Angular's
+`BrowserAnimationsModule` if you want to have animations or the
+`NoopAnimationsModule` if you don't.
+
+```typescript
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { DtQuickFilterModule } from '@dynatrace/barista-components/experimental/quick-filter';
+
+@NgModule({
+ imports: [BrowserAnimationsModule, DtQuickFilterModule],
+})
+class MyModule {}
+```
+
+To use the quick filter in your template there is the
+`` where you have to bind the data
+source. Every content that will be placed inside the quick filter tag is the
+content for the sidebar. To set the title and the subtitle of the sidebar you
+can leverage the `` and ``
+tags to provide a level of customization.
+
+## Inputs
+
+| Name | Type | Default | Description |
+| --------------- | ------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `dataSource` | `DtQuickFilterDataSource` | | Provide a DataSource to feed data to the filter field and the quick filter. This input is mandatory. |
+| `filters` | `any[][]` | | The currently selected filters. This input can also be used to programmatically add filters to the quick filter and filter field. |
+| `label` | `string` | | The label for the input field. Can be set to something like "Filter by". Will be placed next to the filter icon in the filter field |
+| `clearAllLabel` | `string` | | Label for the "Clear all" button in the filter field. Can be set to something like "Clear all". |
+| `aria-label` | `string` | | Sets the value for the Aria-Label attribute. |
+
+## Outputs
+
+| Name | Type | Description |
+| --------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
+| `filterChanges` | `EventEmitter` | Event emitted when filters have been updated by user interaction. Wont be triggered by programmatic changes |
diff --git a/libs/barista-components/experimental/quick-filter/barista.json b/libs/barista-components/experimental/quick-filter/barista.json
new file mode 100644
index 0000000000..d598b639f5
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/barista.json
@@ -0,0 +1,22 @@
+{
+ "title": "Quick Filter (experimental)",
+ "description": "",
+ "public": true,
+ "contributors": {
+ "dev": [
+ {
+ "name": "Lukas Holzer",
+ "gitHubUser": "lukasholzer"
+ }
+ ],
+ "ux": [
+ {
+ "name": "Xavier Javaloyas",
+ "gitHubUser": "Xavi-J"
+ }
+ ]
+ },
+ "properties": ["work in progress", "experimental"],
+ "related": ["filter field"],
+ "tags": ["filter", "filter field", "quick filter", "angular", "component"]
+}
diff --git a/libs/barista-components/experimental/quick-filter/index.ts b/libs/barista-components/experimental/quick-filter/index.ts
new file mode 100644
index 0000000000..cf545b7620
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/index.ts
@@ -0,0 +1,20 @@
+/**
+ * @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.
+ */
+
+export * from './src/quick-filter';
+export * from './src/quick-filter-data-source';
+export * from './src/quick-filter-default-data-source';
+export * from './src/quick-filter.module';
diff --git a/libs/barista-components/experimental/quick-filter/jest.config.js b/libs/barista-components/experimental/quick-filter/jest.config.js
new file mode 100644
index 0000000000..eb94995fb4
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/jest.config.js
@@ -0,0 +1,11 @@
+module.exports = {
+ name: 'quick-filter',
+ preset: '../../../../jest.config.js',
+ coverageDirectory:
+ '../../../../coverage/components/experimental/quick-filter',
+ snapshotSerializers: [
+ 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
+ 'jest-preset-angular/build/AngularSnapshotSerializer.js',
+ 'jest-preset-angular/build/HTMLCommentSerializer.js',
+ ],
+};
diff --git a/libs/barista-components/experimental/quick-filter/package.json b/libs/barista-components/experimental/quick-filter/package.json
new file mode 100644
index 0000000000..dedb72ce9c
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/package.json
@@ -0,0 +1,7 @@
+{
+ "ngPackage": {
+ "lib": {
+ "entryFile": "index.ts"
+ }
+ }
+}
diff --git a/libs/barista-components/experimental/quick-filter/src/quick-filter-data-source.ts b/libs/barista-components/experimental/quick-filter/src/quick-filter-data-source.ts
new file mode 100644
index 0000000000..e82da3850d
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/quick-filter-data-source.ts
@@ -0,0 +1,95 @@
+/**
+ * @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 { Observable } from 'rxjs';
+import {
+ DtNodeDef,
+ DtFilterFieldDataSource,
+} from '@dynatrace/barista-components/filter-field';
+
+export abstract class DtQuickFilterDataSource
+ implements DtFilterFieldDataSource {
+ /**
+ * Used by the DtFilterFieldControl. Called when it connects to the data source.
+ * Should return a stream of data that will be transformed, filtered and
+ * displayed by the DtFilterField and the DtFilterFieldControl.
+ */
+ abstract connect(): Observable;
+
+ /** Used by the DtFilterField. Called when it is destroyed. */
+ abstract disconnect(): void;
+
+ /** Whether the provided data object can be transformed into an DtAutocompleteDef. */
+ abstract isAutocomplete(data: T): boolean;
+
+ /** Whether the provided data object can be transformed into an DtOptionDef. */
+ abstract isOption(data: T): boolean;
+
+ /** Whether the provided data object can be transformed into an DtGroupDef. */
+ abstract isGroup(data: T): boolean;
+
+ /** Whether the provided data object can be transformed into an DtFreeTextDef. */
+ abstract isFreeText(data: T): boolean;
+
+ /** Whether the provided data object can be transformed into an DtRangeDef. */
+ abstract isRange(data: T): boolean;
+
+ abstract showInSidebarFunction = (_node: any): boolean => true;
+
+ /** Transforms the provided data into a DtNodeDef which contains a DtAutocompleteDef. */
+ abstract transformAutocomplete(
+ data: T,
+ parent: DtNodeDef | null,
+ existingDef: DtNodeDef | null,
+ ): DtNodeDef;
+
+ /** Transforms the provided data into a DtNodeDef which contains a DtOptionDef. */
+ abstract transformOption(
+ data: T,
+ parentAutocompleteOrOption: DtNodeDef | null,
+ existingDef: DtNodeDef | null,
+ ): DtNodeDef;
+
+ /** Transforms the provided data into a DtNodeDef which contains a DtGroupDef. */
+ abstract transformGroup(
+ data: T,
+ parentAutocomplete: DtNodeDef | null,
+ existingDef: DtNodeDef | null,
+ ): DtNodeDef;
+
+ /** Transforms the provided data into a DtNodeDef which contains a DtFreeTextDef. */
+ abstract transformFreeText(
+ data: T,
+ parent: DtNodeDef | null,
+ existingDef: DtNodeDef | null,
+ ): DtNodeDef;
+
+ /** Transforms the provided data into a DtNodeDef which contains a DtRangeDef. */
+ abstract transformRange(
+ data: T,
+ parent: DtNodeDef | null,
+ existingDef: DtNodeDef | null,
+ ): DtNodeDef;
+
+ /** Transforms the provided data into a DtNodeDef. */
+ abstract transformObject(
+ data: T | null,
+ parent: DtNodeDef | null,
+ ): DtNodeDef | null;
+
+ /** Transforms the provided list of data objects into an array of DtNodeDefs. */
+ abstract transformList(list: T[], parent: DtNodeDef | null): DtNodeDef[];
+}
diff --git a/libs/barista-components/experimental/quick-filter/src/quick-filter-default-data-source.ts b/libs/barista-components/experimental/quick-filter/src/quick-filter-default-data-source.ts
new file mode 100644
index 0000000000..fadfe97756
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/quick-filter-default-data-source.ts
@@ -0,0 +1,253 @@
+/**
+ * @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 { DtQuickFilterDataSource } from './quick-filter-data-source';
+import { isDefined, isObject } from '@dynatrace/barista-components/core';
+import {
+ DtNodeDef,
+ dtRangeDef,
+ dtFreeTextDef,
+ dtGroupDef,
+ dtOptionDef,
+ isDtAutocompleteDef,
+ DtFilterFieldDefaultDataSourceAutocomplete,
+ DtFilterFieldDefaultDataSourceSimpleGroup,
+ DtFilterFieldDefaultDataSourceGroup,
+ DtFilterFieldDefaultDataSourceFreeText,
+ DtFilterFieldDefaultDataSourceRange,
+ dtAutocompleteDef,
+ isDtGroupDef,
+} from '@dynatrace/barista-components/filter-field';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+export interface DtQuickFilterDefaultDataSourceSimpleOption {
+ name: string;
+}
+
+export type DtQuickFilterDefaultDataSourceRange = DtFilterFieldDefaultDataSourceRange;
+export type DtQuickFilterDefaultDataSourceFreeText = DtFilterFieldDefaultDataSourceFreeText;
+export type DtQuickFilterDefaultDataSourceAutocomplete = DtFilterFieldDefaultDataSourceAutocomplete;
+export type DtQuickFilterDefaultDataSourceGroup = DtFilterFieldDefaultDataSourceGroup;
+export type DtQuickFilterDefaultDataSourceSimpleGroup = DtFilterFieldDefaultDataSourceSimpleGroup;
+
+export type DtQuickFilterDefaultDataSourceType =
+ | DtQuickFilterDefaultDataSourceOption
+ | DtQuickFilterDefaultDataSourceGroup
+ | DtQuickFilterDefaultDataSourceAutocomplete
+ | DtQuickFilterDefaultDataSourceFreeText
+ | DtQuickFilterDefaultDataSourceRange;
+
+export type DtQuickFilterDefaultDataSourceOption =
+ | DtQuickFilterDefaultDataSourceSimpleOption
+ | (DtQuickFilterDefaultDataSourceAutocomplete &
+ DtQuickFilterDefaultDataSourceSimpleOption)
+ | (DtQuickFilterDefaultDataSourceFreeText &
+ DtQuickFilterDefaultDataSourceSimpleOption)
+ | (DtQuickFilterDefaultDataSourceRange &
+ DtQuickFilterDefaultDataSourceSimpleOption);
+
+export interface DtQuickFilterDefaultDataSourceConfig {
+ showInSidebar: (node: any) => boolean;
+}
+
+export class DtQuickFilterDefaultDataSource<
+ T extends DtQuickFilterDefaultDataSourceType
+> implements DtQuickFilterDataSource {
+ private readonly _data$: BehaviorSubject;
+
+ /** Structure of data that is used, transformed and rendered by the filter-field. */
+ get data(): T {
+ return this._data$.value;
+ }
+ set data(data: T) {
+ this._data$.next(data);
+ }
+
+ constructor(
+ initialData: T = (null as unknown) as T,
+ config: DtQuickFilterDefaultDataSourceConfig,
+ ) {
+ this._data$ = new BehaviorSubject(initialData);
+ this.showInSidebarFunction = config.showInSidebar;
+ }
+
+ /** Function that evaluates if a node should be displayed in the quick filter sidebar */
+ showInSidebarFunction: (node: any) => boolean;
+
+ /**
+ * Used by the DtQuickFilter. Called when it connects to the data source.
+ * Should return a stream of data that will be transformed, filtered and
+ * displayed by the DtQuickFilterViewer (filter-field)
+ */
+ connect(): Observable {
+ return this._data$.pipe(map(data => this.transformObject(data)));
+ }
+
+ /** Used by the DtQuickFilter. Called when it is destroyed. No-op. */
+ disconnect(): void {
+ this._data$.complete();
+ }
+
+ /** Whether the provided data object is of type AutocompleteData */
+ isAutocomplete(
+ data: any,
+ ): data is DtQuickFilterDefaultDataSourceAutocomplete {
+ return isObject(data) && Array.isArray(data.autocomplete);
+ }
+
+ /** Whether the provided data object is of type OptionData */
+ isOption(data: any): data is DtQuickFilterDefaultDataSourceOption {
+ return isObject(data) && typeof data.name === 'string';
+ }
+
+ /** Whether the provided data object is of type GroupData */
+ isGroup(data: any): data is DtQuickFilterDefaultDataSourceGroup {
+ return (
+ isObject(data) &&
+ typeof data.name === 'string' &&
+ Array.isArray(data.options)
+ );
+ }
+
+ /** Whether the provided data object is of type FreeTextData */
+ isFreeText(data: any): data is DtQuickFilterDefaultDataSourceFreeText {
+ return isObject(data) && Array.isArray(data.suggestions);
+ }
+
+ /** Whether the provided data object is of type RangeData */
+ isRange(data: any): data is DtQuickFilterDefaultDataSourceRange {
+ return isObject(data) && isObject(data.range);
+ }
+
+ /** Transforms the provided data into a DtNodeDef which contains a DtAutocompleteDef. */
+ transformAutocomplete(
+ data: DtQuickFilterDefaultDataSourceAutocomplete,
+ ): DtNodeDef {
+ const def = dtAutocompleteDef(
+ data,
+ null,
+ [],
+ !!data.distinct,
+ !!data.async,
+ );
+ def.autocomplete!.optionsOrGroups = this.transformList(
+ data.autocomplete,
+ def,
+ );
+ return def;
+ }
+
+ /** Transforms the provided data into a DtNodeDef which contains a DtOptionDef. */
+ transformOption(
+ data: DtQuickFilterDefaultDataSourceOption,
+ parentAutocompleteOrOption: DtNodeDef | null = null,
+ existingDef: DtNodeDef | null = null,
+ ): DtNodeDef {
+ const parentGroup = isDtGroupDef(parentAutocompleteOrOption)
+ ? parentAutocompleteOrOption
+ : null;
+ const parentAutocomplete =
+ parentGroup !== null
+ ? parentGroup.group.parentAutocomplete
+ : isDtAutocompleteDef(parentAutocompleteOrOption)
+ ? (parentAutocompleteOrOption as DtNodeDef)
+ : null;
+ return dtOptionDef(
+ data,
+ existingDef,
+ data.name,
+ null,
+ parentAutocomplete,
+ parentGroup,
+ );
+ }
+
+ /** Transforms the provided data into a DtNodeDef which contains a DtGroupDef. */
+ transformGroup(
+ data: DtQuickFilterDefaultDataSourceGroup,
+ parentAutocomplete: DtNodeDef | null = null,
+ existingDef: DtNodeDef | null = null,
+ ): DtNodeDef {
+ const def = dtGroupDef(
+ data,
+ existingDef,
+ data.name,
+ [],
+ parentAutocomplete,
+ );
+ def.group!.options = this.transformList(data.options, def);
+ return def;
+ }
+
+ /** Transforms the provided data into a DtNodeDef which contains a DtFreeTextDef. */
+ transformFreeText(data: DtQuickFilterDefaultDataSourceFreeText): DtNodeDef {
+ const def = dtFreeTextDef(
+ data,
+ null,
+ [],
+ data.validators,
+ isDefined(data.unique) ? data.unique! : false,
+ );
+ def.freeText!.suggestions = this.transformList(data.suggestions, def);
+ return def;
+ }
+
+ /** Transforms the provided data into a DtNodeDef which contains a DtRangeDef. */
+ transformRange(data: DtQuickFilterDefaultDataSourceRange): DtNodeDef {
+ return dtRangeDef(
+ data,
+ null,
+ !!data.range.operators.range,
+ !!data.range.operators.equal,
+ !!data.range.operators.greaterThanEqual,
+ !!data.range.operators.lessThanEqual,
+ data.range.unit,
+ isDefined(data.unique) ? data.unique! : false,
+ );
+ }
+
+ /** Transforms the provided data into a DtNodeDef. */
+ transformObject(
+ data: DtQuickFilterDefaultDataSourceType | null,
+ parent: DtNodeDef | null = null,
+ ): DtNodeDef | null {
+ let def: DtNodeDef | null = null;
+ if (this.isAutocomplete(data)) {
+ def = this.transformAutocomplete(data);
+ } else if (this.isFreeText(data)) {
+ def = this.transformFreeText(data);
+ } else if (this.isRange(data)) {
+ def = this.transformRange(data);
+ }
+
+ if (this.isGroup(data)) {
+ def = this.transformGroup(data);
+ } else if (this.isOption(data)) {
+ def = this.transformOption(data, parent, def);
+ }
+ return def;
+ }
+
+ /** Transforms the provided list of data objects into an array of DtNodeDefs. */
+ transformList(
+ list: Array,
+ parent: DtNodeDef | null = null,
+ ): DtNodeDef[] {
+ return list
+ .map(item => this.transformObject(item, parent))
+ .filter(item => item !== null) as DtNodeDef[];
+ }
+}
diff --git a/libs/barista-components/experimental/quick-filter/src/quick-filter-group.html b/libs/barista-components/experimental/quick-filter/src/quick-filter-group.html
new file mode 100644
index 0000000000..9dfd8433ad
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/quick-filter-group.html
@@ -0,0 +1,34 @@
+
+
+
+
diff --git a/libs/barista-components/experimental/quick-filter/src/quick-filter-group.scss b/libs/barista-components/experimental/quick-filter/src/quick-filter-group.scss
new file mode 100644
index 0000000000..b7e9db03b3
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/quick-filter-group.scss
@@ -0,0 +1,22 @@
+@import '../../../core/src/style/variables';
+
+:host {
+ display: block;
+ padding: 8px 0;
+}
+
+.dt-quick-filter-group-headline {
+ margin: 0 0 8px;
+ font-size: 18px;
+ font-weight: normal;
+ color: $gray-700;
+}
+
+.dt-quick-filter-group-items {
+ display: flex;
+ flex-flow: column;
+
+ > * {
+ margin-bottom: 8px;
+ }
+}
diff --git a/libs/barista-components/experimental/quick-filter/src/quick-filter-group.ts b/libs/barista-components/experimental/quick-filter/src/quick-filter-group.ts
new file mode 100644
index 0000000000..9529f3bd57
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/quick-filter-group.ts
@@ -0,0 +1,141 @@
+/**
+ * @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 {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ EventEmitter,
+ Input,
+ Output,
+ ViewEncapsulation,
+} from '@angular/core';
+import { DtCheckboxChange } from '@dynatrace/barista-components/checkbox';
+import {
+ DtNodeDef,
+ isDtOptionDef,
+ isDtRenderType,
+} from '@dynatrace/barista-components/filter-field';
+import { DtRadioChange } from '@dynatrace/barista-components/radio';
+import { buildIdPathsFromFilters } from './quick-filter-utils';
+import {
+ Action,
+ addFilter,
+ removeFilter,
+ unsetFilterGroup,
+ updateFilter,
+} from './state/actions';
+
+/** @internal The DtQuickFilterGroup is an internal component */
+@Component({
+ selector: 'dt-quick-filter-group',
+ templateUrl: './quick-filter-group.html',
+ styleUrls: ['./quick-filter-group.scss'],
+ encapsulation: ViewEncapsulation.Emulated,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ preserveWhitespaces: false,
+ host: {
+ class: 'dt-quick-filter-group',
+ },
+})
+export class DtQuickFilterGroup {
+ /** @internal The nodeDef of the autocomplete that should be rendered */
+ @Input('nodeDef') _nodeDef: DtNodeDef;
+
+ /** @internal The list of all active filters */
+ @Input()
+ set activeFilters(filters: any[][]) {
+ this._activeFilterPaths = buildIdPathsFromFilters(filters || []);
+ this._changeDetectorRef.markForCheck();
+ }
+
+ /** @internal Emits a new action that changes the filter */
+ @Output() readonly filterChange = new EventEmitter();
+
+ /** A list of active filter ids */
+ private _activeFilterPaths: string[] = [];
+
+ constructor(private _changeDetectorRef: ChangeDetectorRef) {}
+
+ /** @internal Method that is used to unset a whole filter group */
+ _unsetGroup(): void {
+ this.filterChange.emit(unsetFilterGroup(this._nodeDef));
+ }
+
+ /** @internal Updates a radio box */
+ _selectOption(change: DtRadioChange): void {
+ if (change.value) {
+ this.filterChange.emit(updateFilter(change.value));
+ }
+ }
+
+ /** @internal Select or de select a checkbox */
+ _selectCheckBox(change: DtCheckboxChange): void {
+ const action = change.checked
+ ? addFilter(change.source.value)
+ : removeFilter(change.source.value);
+ this.filterChange.emit(action);
+ }
+
+ /** @internal Helper function that checks if nothing is selected inside a group */
+ _isNothingSelected(): boolean {
+ if (this._nodeDef.option && this._nodeDef.option.uid) {
+ const index = this._activeFilterPaths.findIndex(path =>
+ path.startsWith(this._nodeDef.option!.uid!),
+ );
+ return index === -1;
+ }
+ return false;
+ }
+
+ /** @internal Helper function that checks if an options is active */
+ _isActive(node: DtNodeDef): boolean {
+ return !!(
+ node.option && this._activeFilterPaths.includes(node.option.uid || '')
+ );
+ }
+
+ /**
+ * @internal
+ * Helper function that checks if the nodeDef of the autocomplete is distinct.
+ * Needed to differentiate between radios or checkboxes.
+ */
+ _isDistinct(): boolean {
+ return !!(
+ this._nodeDef.autocomplete && this._nodeDef.autocomplete.distinct
+ );
+ }
+
+ /** @internal Helper function that returns safely the viewValue of a nodeDef */
+ _getViewValue(nodeDef: DtNodeDef): string {
+ return nodeDef && nodeDef.option ? nodeDef.option.viewValue : '';
+ }
+
+ /**
+ * @internal
+ * Helper function that returns all options that can be displayed.
+ * They can only be displayed if it is an option of an autocomplete that
+ * is not a render type so only have a text.
+ */
+ _getOptions(): DtNodeDef[] {
+ if (this._nodeDef && this._nodeDef.autocomplete) {
+ return this._nodeDef.autocomplete.optionsOrGroups.filter(
+ def => isDtOptionDef(def) && !isDtRenderType(def),
+ );
+ }
+ return [];
+ }
+}
diff --git a/libs/barista-components/experimental/quick-filter/src/quick-filter-utils.ts b/libs/barista-components/experimental/quick-filter/src/quick-filter-utils.ts
new file mode 100644
index 0000000000..be553fd585
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/quick-filter-utils.ts
@@ -0,0 +1,26 @@
+/**
+ * @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 { DELIMITER } 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}`,
+ ),
+ );
+}
diff --git a/libs/barista-components/experimental/quick-filter/src/quick-filter.html b/libs/barista-components/experimental/quick-filter/src/quick-filter.html
new file mode 100644
index 0000000000..955b9931c7
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/quick-filter.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libs/barista-components/experimental/quick-filter/src/quick-filter.module.ts b/libs/barista-components/experimental/quick-filter/src/quick-filter.module.ts
new file mode 100644
index 0000000000..b1379045ce
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/quick-filter.module.ts
@@ -0,0 +1,43 @@
+/**
+ * @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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { DtCheckboxModule } from '@dynatrace/barista-components/checkbox';
+import { DtDrawerModule } from '@dynatrace/barista-components/drawer';
+import { DtFilterFieldModule } from '@dynatrace/barista-components/filter-field';
+import { DtRadioModule } from '@dynatrace/barista-components/radio';
+import {
+ DtQuickFilterSubTitle,
+ DtQuickFilterTitle,
+ DtQuickFilter,
+} from './quick-filter';
+import { DtQuickFilterGroup } from './quick-filter-group';
+
+const COMPONENTS = [DtQuickFilter, DtQuickFilterSubTitle, DtQuickFilterTitle];
+
+@NgModule({
+ imports: [
+ CommonModule,
+ DtDrawerModule,
+ DtFilterFieldModule,
+ DtCheckboxModule,
+ DtRadioModule,
+ ],
+ exports: COMPONENTS,
+ declarations: [...COMPONENTS, DtQuickFilterGroup],
+})
+export class DtQuickFilterModule {}
diff --git a/libs/barista-components/experimental/quick-filter/src/quick-filter.scss b/libs/barista-components/experimental/quick-filter/src/quick-filter.scss
new file mode 100644
index 0000000000..6ef02c51bd
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/quick-filter.scss
@@ -0,0 +1,67 @@
+@import '../../../core/src/style/variables';
+@import '../../../core/src/style/interactive-common';
+
+:host {
+ display: block;
+ background-color: $gray-130;
+ padding: 20px;
+}
+
+.dt-quick-filter-drawer {
+ margin-top: 12px;
+}
+
+.dt-quick-filter-content {
+ position: relative;
+ display: block;
+ padding: 12px 12px 12px 26px;
+ height: 100%;
+ background-color: white;
+ border-radius: 3px;
+}
+
+.dt-drawer {
+ background-color: $gray-130;
+ position: relative;
+ padding-right: 26px;
+}
+
+.dt-quick-filter-title {
+ font-size: 20px;
+ color: $gray-700;
+ margin: 0;
+}
+
+.dt-quick-filter-sub-title {
+ font-size: 12px;
+ color: $gray-500;
+ margin: 0;
+}
+
+.dt-quick-filter-group + .dt-quick-filter-group {
+ border-top: 1px solid $gray-200;
+}
+
+.dt-quick-filter-open,
+.dt-quick-filter-close {
+ top: 8px;
+ background: white;
+ border-radius: 3px;
+ position: absolute;
+ color: $turquoise-600;
+ font-size: 1.2em;
+ cursor: pointer;
+}
+
+.dt-quick-filter-open {
+ left: -4px;
+ padding: 6px 6px 6px 0;
+ border-left: none;
+ background-color: $gray-130;
+}
+
+.dt-quick-filter-close {
+ right: -2px;
+ padding: 6px 0 6px 6px;
+ border-right: none;
+}
diff --git a/libs/barista-components/experimental/quick-filter/src/quick-filter.spec.ts b/libs/barista-components/experimental/quick-filter/src/quick-filter.spec.ts
new file mode 100644
index 0000000000..6975dc5b19
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/quick-filter.spec.ts
@@ -0,0 +1,221 @@
+/**
+ * @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.
+ */
+
+// tslint:disable no-lifecycle-call no-use-before-declare no-magic-numbers
+// tslint:disable no-any max-file-line-count no-unbound-method use-component-selector
+
+import { HttpClient } from '@angular/common/http';
+import { Component, DebugElement } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { DtCheckbox } from '@dynatrace/barista-components/checkbox';
+import { DtFilterField } from '@dynatrace/barista-components/filter-field';
+import { DtIconModule } from '@dynatrace/barista-components/icon';
+import {
+ createComponent,
+ dispatchMouseEvent,
+} from '@dynatrace/testing/browser';
+import { FILTER_FIELD_TEST_DATA } from '@dynatrace/testing/fixtures';
+import { of } from 'rxjs';
+import { DtQuickFilter, DtQuickFilterChangeEvent } from './quick-filter';
+import { DtQuickFilterDefaultDataSource } from './quick-filter-default-data-source';
+import { DtQuickFilterModule } from './quick-filter.module';
+
+describe('dt-quick-filter', () => {
+ let instanceDebugElement: DebugElement;
+ let quickFilterInstance: DtQuickFilter;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ DtQuickFilterModule,
+ NoopAnimationsModule,
+ DtIconModule.forRoot({ svgIconLocation: '{{name}}.svg' }),
+ ],
+ declarations: [QuickFilterSimpleComponent, QuickFilterDefaultComponent],
+ providers: [
+ {
+ provide: HttpClient,
+ useValue: {
+ get: jest.fn().mockReturnValue(of('')),
+ },
+ },
+ ],
+ });
+ TestBed.compileComponents();
+ });
+
+ describe('Simple QuickFilter without dataSource', () => {
+ let fixture: ComponentFixture;
+ beforeEach(() => {
+ fixture = createComponent(QuickFilterSimpleComponent);
+ instanceDebugElement = fixture.debugElement.query(
+ By.directive(DtQuickFilter),
+ );
+ quickFilterInstance = instanceDebugElement.injector.get(
+ DtQuickFilter,
+ );
+ });
+
+ it('should have an empty filters array if no dataSource is set', () => {
+ expect(quickFilterInstance.filters).toHaveLength(0);
+ });
+ });
+
+ describe('Normal QuickFilter with mixed dataSource', () => {
+ let fixture: ComponentFixture;
+ let filterFieldDebugElement: DebugElement;
+ let filterFieldInstance: DtFilterField;
+
+ beforeEach(() => {
+ fixture = createComponent(QuickFilterDefaultComponent);
+ instanceDebugElement = fixture.debugElement.query(
+ By.directive(DtQuickFilter),
+ );
+ quickFilterInstance = instanceDebugElement.injector.get(
+ DtQuickFilter,
+ );
+
+ filterFieldDebugElement = fixture.debugElement.query(
+ By.directive(DtFilterField),
+ );
+ filterFieldInstance = filterFieldDebugElement.injector.get(
+ DtFilterField,
+ );
+ });
+
+ it('should set the filters on the filter field if they are set on the quick-filter', () => {
+ expect(quickFilterInstance.filters).toHaveLength(0);
+ const filters = [
+ [
+ FILTER_FIELD_TEST_DATA.autocomplete[0],
+ FILTER_FIELD_TEST_DATA.autocomplete[0].autocomplete![0],
+ ],
+ ];
+
+ quickFilterInstance.filters = filters;
+ fixture.detectChanges();
+ expect(filterFieldInstance.filters).toMatchObject(filters);
+ });
+
+ it('should reset the filters if the data source gets switched.', () => {
+ quickFilterInstance.filters = [
+ [
+ FILTER_FIELD_TEST_DATA.autocomplete[0],
+ FILTER_FIELD_TEST_DATA.autocomplete[0].autocomplete![0],
+ ],
+ ];
+ fixture.detectChanges();
+
+ fixture.componentInstance._dataSource = new DtQuickFilterDefaultDataSource(
+ FILTER_FIELD_TEST_DATA,
+ {
+ showInSidebar: () => true,
+ },
+ );
+ fixture.detectChanges();
+
+ expect(filterFieldInstance.filters).toMatchObject([]);
+ expect(quickFilterInstance.filters).toMatchObject([]);
+ });
+
+ it('should filter the groups that should be displayed in the sidebar dynamically', () => {
+ let groups = getGroupHeadlines(fixture.debugElement);
+ expect(groups).toHaveLength(3);
+ expect(groups).toMatchObject(['AUT', 'USA', 'Not in Quickfilter']);
+
+ fixture.detectChanges();
+
+ fixture.componentInstance._dataSource = new DtQuickFilterDefaultDataSource(
+ FILTER_FIELD_TEST_DATA,
+ {
+ showInSidebar: node => node.name !== 'Not in Quickfilter',
+ },
+ );
+ fixture.detectChanges();
+ groups = getGroupHeadlines(fixture.debugElement);
+
+ expect(groups).toHaveLength(2);
+ expect(groups).toMatchObject(['AUT', 'USA']);
+ });
+
+ it('should dispatch an event with the changes on selecting an option', () => {
+ const changeSpy = jest.spyOn(fixture.componentInstance, 'filterChanges');
+ expect(changeSpy).toHaveBeenCalledTimes(0);
+ const checkboxes = fixture.debugElement
+ .queryAll(By.directive(DtCheckbox))
+ .map(el => el.query(By.css('label')));
+
+ dispatchMouseEvent(checkboxes[1].nativeElement, 'click');
+ fixture.detectChanges();
+
+ expect(changeSpy).toHaveBeenCalledTimes(1);
+ expect(changeSpy.mock.calls[0][0].filters).toMatchObject([
+ [
+ FILTER_FIELD_TEST_DATA.autocomplete[1],
+ FILTER_FIELD_TEST_DATA.autocomplete[1].autocomplete![1],
+ ],
+ ]);
+ changeSpy.mockClear();
+ });
+ });
+});
+
+/** Get all quick filter group item headlines */
+function getGroupHeadlines(debugElement: DebugElement): string[] {
+ return debugElement
+ .queryAll(By.css('.dt-quick-filter-group-headline'))
+ .map(el => el.nativeElement.textContent);
+}
+
+@Component({
+ selector: 'dt-quick-filter-simple',
+ template: `
+
+ `,
+})
+class QuickFilterSimpleComponent {}
+
+@Component({
+ selector: 'dt-quick-filter-simple',
+ template: `
+
+ Quick-filter
+
+ All options in the filter field above
+
+
+ my content
+
+ `,
+})
+class QuickFilterDefaultComponent {
+ filterFn = () => true;
+ label = 'Filter by';
+ clearAllLabel = 'Clear all';
+
+ filterChanges(_event: DtQuickFilterChangeEvent): void {}
+
+ _dataSource = new DtQuickFilterDefaultDataSource(FILTER_FIELD_TEST_DATA, {
+ showInSidebar: this.filterFn,
+ });
+}
diff --git a/libs/barista-components/experimental/quick-filter/src/quick-filter.ts b/libs/barista-components/experimental/quick-filter/src/quick-filter.ts
new file mode 100644
index 0000000000..f3ff3a07e6
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/quick-filter.ts
@@ -0,0 +1,170 @@
+/**
+ * @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 {
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ Component,
+ Directive,
+ EventEmitter,
+ Input,
+ OnDestroy,
+ Output,
+ ViewChild,
+ ViewEncapsulation,
+} from '@angular/core';
+import {
+ DtFilterField,
+ DtFilterFieldChangeEvent,
+} from '@dynatrace/barista-components/filter-field';
+import { Subject } from 'rxjs';
+import { takeUntil } from 'rxjs/operators';
+import { DtQuickFilterDataSource } from './quick-filter-data-source';
+import { Action, setFilters, switchDataSource } from './state/actions';
+import { quickFilterReducer } from './state/reducer';
+import { getAutocompletes, getDataSource, getFilters } from './state/selectors';
+import { createQuickFilterStore } from './state/store';
+
+/** Directive that is used to place a title inside the quick filters side bar */
+@Directive({
+ selector: 'dt-quick-filter-title',
+ exportAs: 'dtQuickFilterTitle',
+ host: {
+ class: 'dt-quick-filter-title',
+ },
+})
+export class DtQuickFilterTitle {}
+
+/** Directive that is used to place a subtitle inside the quick filters side bar */
+@Directive({
+ selector: 'dt-quick-filter-sub-title',
+ exportAs: 'dtQuickFilterSubTitle',
+ host: {
+ class: 'dt-quick-filter-sub-title',
+ },
+})
+export class DtQuickFilterSubTitle {}
+
+/**
+ * The `DtQuickFilterChangeEvent` is a class that is used to transport data.
+ * It contains the added and removed filters as the current set of all filters.
+ */
+export class DtQuickFilterChangeEvent extends DtFilterFieldChangeEvent {}
+
+@Component({
+ selector: 'dt-quick-filter',
+ exportAs: 'dtQuickFilter',
+ templateUrl: 'quick-filter.html',
+ styleUrls: ['quick-filter.scss'],
+ host: {
+ class: 'dt-quick-filter',
+ },
+ preserveWhitespaces: false,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ encapsulation: ViewEncapsulation.Emulated,
+})
+export class DtQuickFilter implements AfterViewInit, OnDestroy {
+ /** Emits when a new filter has been added or removed. */
+ @Output() readonly filterChanges = new EventEmitter<
+ DtQuickFilterChangeEvent
+ >();
+
+ /**
+ * @internal
+ * Instance of the filter field that will be orchestrated by the quick filter
+ */
+ @ViewChild(DtFilterField, { static: true })
+ _filterField: DtFilterField;
+
+ /**
+ * Label for the filter field (e.g. "Filter by").
+ * Will be placed next to the filter icon in the filter field
+ */
+ @Input() label = '';
+
+ /** Label for the "Clear all" button in the filter field (e.g. "Clear all"). */
+ @Input() clearAllLabel = '';
+
+ /** Set the Aria-Label attribute */
+ @Input('aria-label') ariaLabel = '';
+
+ /** The data source instance that should be connected to the filter field. */
+ @Input()
+ set dataSource(dataSource: DtQuickFilterDataSource) {
+ this._store.dispatch(switchDataSource(dataSource));
+ }
+
+ /** The currently applied filters */
+ @Input()
+ get filters(): T[][] {
+ return this._filterField.filters;
+ }
+ set filters(filters: T[][]) {
+ this._store.dispatch(setFilters(filters));
+ }
+
+ /** The store where the data flow is managed */
+ private _store = createQuickFilterStore(quickFilterReducer);
+
+ /** @internal the autocomplete fields that should be rendered by the quick filter */
+ readonly _autocompleteData$ = this._store.select(getAutocompletes);
+ /** @internal the dataSource that gets passed to the filter field */
+ readonly _filterFieldDataSource$ = this._store.select(getDataSource);
+ /** @internal the list of all current active filters */
+ readonly _activeFilters$ = this._store.select(getFilters);
+
+ /** Subject that is used for bulk unsubscribing */
+ private _destroy$ = new Subject();
+
+ /** Angular life-cycle hook that will be called after the view is initialized */
+ ngAfterViewInit(): void {
+ // When the filters changes apply them to the filter field
+ this._activeFilters$.pipe(takeUntil(this._destroy$)).subscribe(filters => {
+ if (this._filterField.filters !== filters) {
+ this._filterField.filters = filters;
+ }
+ });
+ }
+
+ /** Angular life-cycle hook that will be called on component destroy */
+ ngOnDestroy(): void {
+ this._destroy$.next();
+ this._destroy$.complete();
+ }
+
+ /**
+ * @internal
+ * When the user selects an option in the quick filter an action gets passed
+ * to this function that will be dispatched to the store
+ */
+ _changeFilter(action: Action): void {
+ this._store.dispatch(action);
+ this.filterChanges.emit(
+ new DtQuickFilterChangeEvent(
+ this._filterField,
+ [],
+ [],
+ this._filterField.filters,
+ ),
+ );
+ }
+
+ /** @internal Bubble the filter field change event through */
+ _filterFiledChanged(change: DtFilterFieldChangeEvent): void {
+ this._store.dispatch(setFilters(change.filters));
+ this.filterChanges.emit(change);
+ }
+}
diff --git a/libs/barista-components/experimental/quick-filter/src/state/actions.ts b/libs/barista-components/experimental/quick-filter/src/state/actions.ts
new file mode 100644
index 0000000000..e9ce277c4d
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/state/actions.ts
@@ -0,0 +1,62 @@
+/**
+ * @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 {
+ DtFilterFieldDataSource,
+ DtNodeDef,
+} from '@dynatrace/barista-components/filter-field';
+
+export enum ActionType {
+ INIT = '@@actions init',
+ ADD_FILTER = '@@actions add filter',
+ REMOVE_FILTER = '@@actions remove filter',
+ UPDATE_FILTER = '@@actions update filter',
+ SET_FILTERS = '@@actions set filters',
+ UNSET_FILTER_GROUP = '@@actions unset filter group',
+ SWITCH_DATA_SOURCE = '@@actions switch dataSource',
+ UPDATE_DATA_SOURCE = '@@actions update dataSource',
+}
+
+/** Interface for an action */
+export interface Action {
+ readonly type: ActionType;
+ payload?: T;
+}
+/** Function which helps to create actions without mistakes */
+export const action = (type: ActionType, payload?: T): Action => ({
+ type,
+ payload,
+});
+
+export const setFilters = (filters: any[][]) =>
+ action(ActionType.SET_FILTERS, filters);
+
+export const unsetFilterGroup = (group: DtNodeDef) =>
+ action(ActionType.UNSET_FILTER_GROUP, group);
+
+export const addFilter = (item: DtNodeDef) =>
+ action(ActionType.ADD_FILTER, item);
+
+export const removeFilter = (item: DtNodeDef) =>
+ action(ActionType.REMOVE_FILTER, item);
+
+export const updateFilter = (item: DtNodeDef) =>
+ action(ActionType.UPDATE_FILTER, item);
+
+export const switchDataSource = (item: DtFilterFieldDataSource) =>
+ action(ActionType.SWITCH_DATA_SOURCE, item);
+
+export const updateDataSource = (nodeDef: DtNodeDef) =>
+ action(ActionType.UPDATE_DATA_SOURCE, nodeDef);
diff --git a/libs/barista-components/experimental/quick-filter/src/state/effects.ts b/libs/barista-components/experimental/quick-filter/src/state/effects.ts
new file mode 100644
index 0000000000..3d4cda5067
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/state/effects.ts
@@ -0,0 +1,46 @@
+/**
+ * @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 {
+ DtFilterFieldDataSource,
+ DtNodeDef,
+} from '@dynatrace/barista-components/filter-field';
+import { MonoTypeOperatorFunction, Observable } from 'rxjs';
+import { filter, map, switchMap } from 'rxjs/operators';
+import { Action, ActionType, updateDataSource } from './actions';
+import { QuickFilterState } from './store';
+
+/** Type for an effect */
+export type Effect = (
+ action$: Observable,
+ state$?: Observable,
+) => Observable;
+
+/** Operator to filter actions */
+export const ofType = (
+ ...types: ActionType[]
+): MonoTypeOperatorFunction> =>
+ filter((action: Action) => types.indexOf(action.type) > -1);
+
+/** Connects to a new Data dataSource */
+export const switchDataSourceEffect: Effect = (action$: Observable) =>
+ action$.pipe(
+ ofType(ActionType.SWITCH_DATA_SOURCE),
+ switchMap(action => {
+ return action.payload!.connect();
+ }),
+ map((nodeDef: DtNodeDef) => updateDataSource(nodeDef)),
+ );
diff --git a/libs/barista-components/experimental/quick-filter/src/state/reducer.spec.ts b/libs/barista-components/experimental/quick-filter/src/state/reducer.spec.ts
new file mode 100644
index 0000000000..a4f3d9363d
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/state/reducer.spec.ts
@@ -0,0 +1,30 @@
+/**
+ * @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 { dtAutocompleteDef } from '@dynatrace/barista-components/filter-field';
+import { buildData } from './reducer';
+
+test('should build the data with an object', () => {
+ const data = { name: 'Linz' };
+ const autocomplete = dtAutocompleteDef(data, null, [], false, false);
+ expect(buildData(autocomplete)).toMatchObject([data]);
+});
+
+test('should build the data with undefined', () => {
+ const data = undefined;
+ const autocomplete = dtAutocompleteDef(data, null, [], false, false);
+
+ expect(buildData(autocomplete)).toMatchObject([data]);
+});
diff --git a/libs/barista-components/experimental/quick-filter/src/state/reducer.ts b/libs/barista-components/experimental/quick-filter/src/state/reducer.ts
new file mode 100644
index 0000000000..a234d379a0
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/state/reducer.ts
@@ -0,0 +1,153 @@
+/**
+ * @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 { DtLogger, DtLoggerFactory } from '@dynatrace/barista-components/core';
+import {
+ DELIMITER,
+ DtNodeDef,
+} from '@dynatrace/barista-components/filter-field';
+import { Action, ActionType } from './actions';
+import { initialState, QuickFilterState } from './store';
+
+const logger: DtLogger = DtLoggerFactory.create('DtQuickFilter State');
+
+/** @internal Type of a reducer */
+export type Reducer = (
+ state: QuickFilterState,
+ action: Action,
+) => QuickFilterState;
+
+/**
+ * @internal
+ * The Quick Filter reducer is the place where we handle all the state updates
+ * To have a single entry point. Every action can trigger an update of the state.
+ * It has to be a immutable function that always returns a new object of the state.
+ * @param state The state that should be modified.
+ * @param action The current action that should be handled.
+ */
+export function quickFilterReducer(
+ state: QuickFilterState,
+ action: Action,
+): QuickFilterState {
+ logger.debug(`Reducer <${action.type}> `, action);
+
+ switch (action.type) {
+ case ActionType.SWITCH_DATA_SOURCE:
+ if (state.dataSource) {
+ state.dataSource.disconnect();
+ }
+ return { ...initialState, dataSource: action.payload };
+ case ActionType.UPDATE_DATA_SOURCE:
+ return { ...state, nodeDef: action.payload };
+ case ActionType.SET_FILTERS:
+ return { ...state, filters: action.payload };
+ case ActionType.UNSET_FILTER_GROUP:
+ return {
+ ...state,
+ filters: unsetFilterGroup(state.filters, action.payload),
+ };
+ case ActionType.ADD_FILTER:
+ return { ...state, filters: addFilter(state.filters, action.payload) };
+ case ActionType.UPDATE_FILTER:
+ return { ...state, filters: updateFilter(state.filters, action.payload) };
+ case ActionType.REMOVE_FILTER:
+ return { ...state, filters: removeFilter(state.filters, action.payload) };
+ default:
+ // Default return the same state as it was passed so don't modify anything
+ return state;
+ }
+}
+
+// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+// #
+// # HELPER functions for modifying the filters
+// #
+// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+/** @internal Add a filter to the filters array */
+export function addFilter(filters: any[][], item: DtNodeDef): any[][] {
+ return [...filters, buildData(item)];
+}
+
+/** @internal Remove a filter from the filters array */
+export function removeFilter(filters: any[][], item: DtNodeDef): any[][] {
+ const index = findSelectedOption(filters, item, false);
+ const updatedState = [...filters];
+
+ if (index > -1) {
+ delete updatedState[index];
+ }
+
+ return updatedState.filter(Boolean);
+}
+
+/** @internal Update a filter inside the filters array */
+export function updateFilter(filters: any[][], item: DtNodeDef): any[][] {
+ const index = findSelectedOption(filters, item, true);
+
+ if (index < 0) {
+ return addFilter(filters, item);
+ }
+
+ filters[index] = buildData(item);
+
+ return filters;
+}
+
+/** @internal Remove a group from the filters array */
+export function unsetFilterGroup(filters: any[][], group: DtNodeDef): any[][] {
+ if (group.option && group.option.viewValue) {
+ return filters.filter(filter => filter[0].name !== group.option!.viewValue);
+ }
+ return filters;
+}
+
+/** @internal Add a filter to the filters array */
+export function buildData(item: DtNodeDef): any[] {
+ const data = [item.data];
+
+ if (item.option && item.option.parentAutocomplete) {
+ data.unshift(item.option.parentAutocomplete.data);
+ }
+ return data;
+}
+
+/** @internal Find a filter inside the filters array based on a NodeDef */
+export function findSelectedOption(
+ filters: any[][],
+ item: DtNodeDef,
+ distinct: boolean = false,
+): number {
+ return filters.findIndex(path => {
+ if (item.option && item.option.uid) {
+ const parts = item.option.uid.split(DELIMITER);
+
+ if (distinct && parts[0] === path[0].name) {
+ return true;
+ }
+
+ const dataPath = path.reduce(
+ (previousValue, currentValue) =>
+ `${previousValue.name}${DELIMITER}${currentValue.name}${DELIMITER}`,
+ );
+
+ if (item.option.uid === dataPath) {
+ return true;
+ }
+ }
+
+ return false;
+ });
+}
diff --git a/libs/barista-components/experimental/quick-filter/src/state/selectors.ts b/libs/barista-components/experimental/quick-filter/src/state/selectors.ts
new file mode 100644
index 0000000000..9bc18159fd
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/state/selectors.ts
@@ -0,0 +1,55 @@
+/**
+ * @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 {
+ applyDtOptionIds,
+ DtNodeDef,
+ isDtAutocompleteDef,
+} from '@dynatrace/barista-components/filter-field';
+import { Observable } from 'rxjs';
+import { filter, map, pluck, tap, withLatestFrom } from 'rxjs/operators';
+import { DtQuickFilterDataSource } from '../quick-filter-data-source';
+import { QuickFilterState } from './store';
+
+/** @internal Select all autocompletes from the root Node Def out of the store */
+export const getAutocompletes = (
+ state$: Observable,
+): Observable =>
+ state$.pipe(
+ tap(state => {
+ // apply the ids to the node to identify them later on
+ if (state.nodeDef) {
+ applyDtOptionIds(state.nodeDef);
+ }
+ }),
+ pluck('nodeDef'),
+ filter(isDtAutocompleteDef),
+ withLatestFrom(
+ getDataSource(state$).pipe(filter(Boolean)),
+ ),
+ map(([{ autocomplete }, { showInSidebarFunction }]) =>
+ autocomplete.optionsOrGroups.filter(
+ node => isDtAutocompleteDef(node) && showInSidebarFunction(node.data),
+ ),
+ ),
+ );
+
+/** @internal Select the data Source from the store */
+export const getDataSource = (state$: Observable) =>
+ state$.pipe(pluck('dataSource'));
+
+/** @internal Select the actual applied filters */
+export const getFilters = (state$: Observable) =>
+ state$.pipe(pluck('filters'));
diff --git a/libs/barista-components/experimental/quick-filter/src/state/store.ts b/libs/barista-components/experimental/quick-filter/src/state/store.ts
new file mode 100644
index 0000000000..221a1e65d9
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/state/store.ts
@@ -0,0 +1,106 @@
+/**
+ * @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 { DtNodeDef } from '@dynatrace/barista-components/filter-field';
+import { BehaviorSubject, merge, Observable } from 'rxjs';
+import { map, shareReplay, withLatestFrom } from 'rxjs/operators';
+import { DtQuickFilterDataSource } from '../quick-filter-data-source';
+import { Action, ActionType } from './actions';
+import { Effect, switchDataSourceEffect } from './effects';
+import { Reducer } from './reducer';
+
+/** @internal Interface that describes the QuickFilter state */
+export interface QuickFilterState {
+ /** The root NodeDef of the dataSource */
+ nodeDef?: DtNodeDef;
+ /** The dataSource that is connected with the QuickFilter */
+ dataSource?: DtQuickFilterDataSource;
+ /** Array of all active filters */
+ filters: any[][];
+}
+
+/** @internal The initial QuickFilter state */
+export const initialState: QuickFilterState = {
+ filters: [],
+};
+
+/** Array of side effects */
+const effects: Effect[] = [switchDataSourceEffect];
+
+/**
+ * The Quick Filter Store is one place where the state is handled.
+ * It is a minimal implementation of a redux like architecture to handle
+ * state in an immutable and on-directional way.
+ *
+ * This makes testing and debugging way easier, because there is always a
+ * clear state that can only be modified through actions.
+ *
+ * 1. Action gets dispatched (An action indicates a change in the store)
+ * 2. The reducer gets an action and the current state and according to the action
+ * modifies the state.
+ * 3. A Selector can always read the latest value from the store and displays it in
+ * a template. So the only way to modify the state is dispatching an action.
+ * 4. If some async work has to be done the effect is responsible for that.
+ * Effects are listening for actions then doing some async work and dispatching some
+ * Other actions with the payload of the async stuff.
+ */
+class QuickFilterStore {
+ /** The current action that got dispatched */
+ private readonly action$ = new BehaviorSubject({
+ type: ActionType.INIT,
+ });
+
+ /** The current state that is present */
+ private readonly state$: BehaviorSubject;
+
+ constructor(reducer: Reducer, initialStoreState: QuickFilterState) {
+ this.state$ = new BehaviorSubject(initialStoreState);
+
+ this.action$
+ .pipe(
+ shareReplay(),
+ withLatestFrom(this.state$),
+ map(([action, state]) => reducer(state, action)),
+ )
+ .subscribe(state => {
+ // Here the state gets modified through the outcome of the reducer
+ this.state$.next(state);
+ });
+
+ // Each effect will get the stream of actions and will dispatch other actions in return
+ // The emitted actions will be immediately dispatched through the normal store.dispatch()
+ merge(...effects.map(epic => epic(this.action$, this.state$))).subscribe(
+ (action: Action) => {
+ this.dispatch(action);
+ },
+ );
+ }
+
+ /** Dispatch a new Action that modifies the store */
+ dispatch(action: Action): void {
+ this.action$.next(action);
+ }
+
+ /** Use a provided selector function to get a State out of the store */
+ select(selector: (state$: Observable) => T): T {
+ return selector(this.state$);
+ }
+}
+
+/** @internal This function creates the store for the quick filter */
+export function createQuickFilterStore(reducer: Reducer): QuickFilterStore {
+ return new QuickFilterStore(reducer, initialState);
+}
diff --git a/libs/barista-components/experimental/quick-filter/src/test-setup.ts b/libs/barista-components/experimental/quick-filter/src/test-setup.ts
new file mode 100644
index 0000000000..6381bbc656
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/src/test-setup.ts
@@ -0,0 +1,18 @@
+/**
+ * @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 'jest-preset-angular';
+import '@angular/localize/init';
diff --git a/libs/barista-components/experimental/quick-filter/tsconfig.json b/libs/barista-components/experimental/quick-filter/tsconfig.json
new file mode 100644
index 0000000000..a6233e13a0
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../../../tsconfig.json",
+ "compilerOptions": {
+ "types": ["node", "jest"]
+ },
+ "include": ["**/*.ts"]
+}
diff --git a/libs/barista-components/experimental/quick-filter/tsconfig.lib.json b/libs/barista-components/experimental/quick-filter/tsconfig.lib.json
new file mode 100644
index 0000000000..b639cbf40a
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/tsconfig.lib.json
@@ -0,0 +1,20 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../../dist/out-tsc",
+ "target": "es2015",
+ "declaration": true,
+ "inlineSources": true,
+ "types": [],
+ "lib": ["dom", "es2018"]
+ },
+ "angularCompilerOptions": {
+ "annotateForClosureCompiler": true,
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true,
+ "fullTemplateTypeCheck": true,
+ "strictInjectionParameters": true,
+ "enableResourceInlining": true
+ },
+ "exclude": ["src/test-setup.ts", "**/*.spec.ts"]
+}
diff --git a/libs/barista-components/experimental/quick-filter/tsconfig.spec.json b/libs/barista-components/experimental/quick-filter/tsconfig.spec.json
new file mode 100644
index 0000000000..aed68bc6bf
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/tsconfig.spec.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../../dist/out-tsc",
+ "module": "commonjs",
+ "types": ["jest", "node"]
+ },
+ "files": ["src/test-setup.ts"],
+ "include": ["**/*.spec.ts", "**/*.d.ts"]
+}
diff --git a/libs/barista-components/experimental/quick-filter/tslint.json b/libs/barista-components/experimental/quick-filter/tslint.json
new file mode 100644
index 0000000000..b20a356378
--- /dev/null
+++ b/libs/barista-components/experimental/quick-filter/tslint.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../../../tslint.json",
+ "rules": {}
+}
diff --git a/libs/barista-components/filter-field/index.ts b/libs/barista-components/filter-field/index.ts
index 4f9b0f832f..30986293e2 100644
--- a/libs/barista-components/filter-field/index.ts
+++ b/libs/barista-components/filter-field/index.ts
@@ -22,7 +22,7 @@ export * from './src/filter-field-range/filter-field-range-trigger';
export * from './src/filter-field-data-source';
export * from './src/filter-field-default-data-source';
export * from './src/filter-field-errors';
-
+export { applyDtOptionIds, DELIMITER } from './src/filter-field-util';
export {
DtNodeFlags,
DtNodeDef,
@@ -43,4 +43,5 @@ export {
isDtOptionDef,
dtGroupDef,
isDtGroupDef,
+ isDtRenderType,
} from './src/types';
diff --git a/libs/barista-components/filter-field/src/filter-field-util.ts b/libs/barista-components/filter-field/src/filter-field-util.ts
index 424dd6501a..f36c0b58d8 100644
--- a/libs/barista-components/filter-field/src/filter-field-util.ts
+++ b/libs/barista-components/filter-field/src/filter-field-util.ts
@@ -409,12 +409,15 @@ export function findDefForSource(
return null;
}
-// Use an obscure Unicode character to delimit the words in the concatenated string.
-// This avoids matches where the values of two columns combined will match the user's query
-// (e.g. `Flute` and `Stop` will match `Test`). The character is intended to be something
-// that has a very low chance of being typed in by somebody in a text field. This one in
-// particular is "White up-pointing triangle with dot" from
-// https://en.wikipedia.org/wiki/List_of_Unicode_characters
+/**
+ * @internal
+ * Use an obscure Unicode character to delimit the words in the concatenated string.
+ * This avoids matches where the values of two columns combined will match the user's query
+ * (e.g. `Flute` and `Stop` will match `Test`). The character is intended to be something
+ * that has a very low chance of being typed in by somebody in a text field. This one in
+ * particular is "White up-pointing triangle with dot" from
+ * https://en.wikipedia.org/wiki/List_of_Unicode_characters
+ */
export const DELIMITER = '◬';
/** Peeks into a option node definition and returns its distinct id or creates a new one. */
diff --git a/libs/barista-components/filter-field/src/filter-field.spec.ts b/libs/barista-components/filter-field/src/filter-field.spec.ts
index 54c11d43fb..0a093e3408 100644
--- a/libs/barista-components/filter-field/src/filter-field.spec.ts
+++ b/libs/barista-components/filter-field/src/filter-field.spec.ts
@@ -17,98 +17,42 @@
// tslint:disable no-lifecycle-call no-use-before-declare no-magic-numbers
// tslint:disable no-any max-file-line-count no-unbound-method use-component-selector
-import { BACKSPACE, ENTER, ESCAPE, DOWN_ARROW } from '@angular/cdk/keycodes';
+import { BACKSPACE, DOWN_ARROW, ENTER, ESCAPE } from '@angular/cdk/keycodes';
import { OverlayContainer } from '@angular/cdk/overlay';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { Component, DebugElement, NgZone, ViewChild } from '@angular/core';
import {
ComponentFixture,
- TestBed,
fakeAsync,
flush,
inject,
+ TestBed,
tick,
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
-
import {
- DT_FILTER_FIELD_TYPING_DEBOUNCE,
DtFilterField,
DtFilterFieldChangeEvent,
DtFilterFieldDefaultDataSource,
DtFilterFieldModule,
dtRangeDef,
+ DT_FILTER_FIELD_TYPING_DEBOUNCE,
getDtFilterFieldRangeNoOperatorsError,
- DtFilterFieldDefaultDataSourceType,
} from '@dynatrace/barista-components/filter-field';
import { DtIconModule } from '@dynatrace/barista-components/icon';
-
import {
+ createComponent,
dispatchFakeEvent,
dispatchKeyboardEvent,
- createComponent,
MockNgZone,
typeInElement,
wrappedErrorMessage,
} from '@dynatrace/testing/browser';
-
-const TEST_DATA = {
- autocomplete: [
- {
- name: 'AUT',
- autocomplete: [
- {
- name: 'Upper Austria',
- distinct: true,
- autocomplete: [
- {
- name: 'Cities',
- options: [{ name: 'Linz' }, { name: 'Wels' }, { name: 'Steyr' }],
- },
- ],
- },
- {
- name: 'Vienna',
- },
- ],
- },
- {
- name: 'USA',
- autocomplete: [{ name: 'Los Angeles' }, { name: 'San Fran' }],
- },
- {
- name: 'Free',
- suggestions: [],
- validators: [],
- },
- {
- name: 'DE (async)',
- async: true,
- autocomplete: [{ name: 'Berlin' }],
- },
- ],
-};
-
-const TEST_DATA_SINGLE_DISTINCT: DtFilterFieldDefaultDataSourceType = {
- autocomplete: [
- {
- name: 'AUT',
- distinct: true,
- autocomplete: [
- {
- name: 'Vienna',
- },
- {
- name: 'Linz',
- },
- ],
- },
- ],
-};
-
-const TEST_DATA_SINGLE_OPTION = {
- autocomplete: [{ name: 'option' }],
-};
+import {
+ FILTER_FIELD_TEST_DATA_ASYNC,
+ FILTER_FIELD_TEST_DATA_SINGLE_DISTINCT,
+ FILTER_FIELD_TEST_DATA_SINGLE_OPTION,
+} from '@dynatrace/testing/fixtures';
const TEST_DATA_SUGGESTIONS = {
autocomplete: [
@@ -269,7 +213,7 @@ describe('DtFilterField', () => {
it('should disable all tags if filter field is disabled', fakeAsync(() => {
// given
- fixture.componentInstance.dataSource.data = TEST_DATA_SINGLE_DISTINCT;
+ fixture.componentInstance.dataSource.data = FILTER_FIELD_TEST_DATA_SINGLE_DISTINCT;
fixture.detectChanges();
filterField.focus();
@@ -307,7 +251,7 @@ describe('DtFilterField', () => {
it('should restore the previous state of tags if filter field gets enabled', fakeAsync(() => {
// given
- fixture.componentInstance.dataSource.data = TEST_DATA;
+ fixture.componentInstance.dataSource.data = FILTER_FIELD_TEST_DATA_ASYNC;
fixture.detectChanges();
// Add filter "AUT - Vienna"
@@ -621,7 +565,7 @@ describe('DtFilterField', () => {
it('should emit filterChanges when adding an option', fakeAsync(() => {
let filterChangeEvent: DtFilterFieldChangeEvent | undefined;
- fixture.componentInstance.dataSource.data = TEST_DATA_SINGLE_OPTION;
+ fixture.componentInstance.dataSource.data = FILTER_FIELD_TEST_DATA_SINGLE_OPTION;
const sub = filterField.filterChanges.subscribe(
ev => (filterChangeEvent = ev),
);
@@ -648,7 +592,7 @@ describe('DtFilterField', () => {
it('should emit filterChanges when removing an option', fakeAsync(() => {
let filterChangeEvent: DtFilterFieldChangeEvent | undefined;
- fixture.componentInstance.dataSource.data = TEST_DATA_SINGLE_OPTION;
+ fixture.componentInstance.dataSource.data = FILTER_FIELD_TEST_DATA_SINGLE_OPTION;
const sub = filterField.filterChanges.subscribe(
ev => (filterChangeEvent = ev),
);
@@ -777,7 +721,7 @@ describe('DtFilterField', () => {
});
it('should show option again after adding all possible options and removing this option from the filters', () => {
- fixture.componentInstance.dataSource.data = TEST_DATA_SINGLE_DISTINCT;
+ fixture.componentInstance.dataSource.data = FILTER_FIELD_TEST_DATA_SINGLE_DISTINCT;
fixture.detectChanges();
filterField.focus();
@@ -848,7 +792,7 @@ describe('DtFilterField', () => {
});
it('should remove a parent from an autocomplete if it is distinct and an option has been selected', () => {
- fixture.componentInstance.dataSource.data = TEST_DATA_SINGLE_DISTINCT;
+ fixture.componentInstance.dataSource.data = FILTER_FIELD_TEST_DATA_SINGLE_DISTINCT;
fixture.detectChanges();
filterField.focus();
zone.simulateMicrotasksEmpty();
@@ -1671,7 +1615,7 @@ describe('DtFilterField', () => {
it('should emit a filterchange event when the edit of a range is completed', () => {
let filterChangeEvent: DtFilterFieldChangeEvent | undefined;
- fixture.componentInstance.dataSource.data = TEST_DATA_SINGLE_OPTION;
+ fixture.componentInstance.dataSource.data = FILTER_FIELD_TEST_DATA_SINGLE_OPTION;
const sub = filterField.filterChanges.subscribe(
ev => (filterChangeEvent = ev),
);
@@ -2193,7 +2137,7 @@ describe('DtFilterField', () => {
it('should not remove the current filter if the data is changed when the filterChanges event fires', () => {
const filterChangesSubscription = filterField.filterChanges.subscribe(
() => {
- fixture.componentInstance.dataSource.data = TEST_DATA;
+ fixture.componentInstance.dataSource.data = FILTER_FIELD_TEST_DATA_ASYNC;
},
);
@@ -2347,7 +2291,9 @@ function isClearAllVisible(fixture: ComponentFixture): boolean {
})
export class TestApp {
// tslint:disable-next-line:no-any
- dataSource = new DtFilterFieldDefaultDataSource(TEST_DATA);
+ dataSource = new DtFilterFieldDefaultDataSource(
+ FILTER_FIELD_TEST_DATA_ASYNC,
+ );
label = 'Filter by';
clearAllLabel = 'Clear all';
diff --git a/libs/barista-components/filter-field/src/filter-field.ts b/libs/barista-components/filter-field/src/filter-field.ts
index fc7cfc2977..dff8d980ee 100644
--- a/libs/barista-components/filter-field/src/filter-field.ts
+++ b/libs/barista-components/filter-field/src/filter-field.ts
@@ -177,7 +177,7 @@ export const DT_FILTER_FIELD_TYPING_DEBOUNCE = 200;
]),
],
})
-export class DtFilterField
+export class DtFilterField
implements CanDisable, AfterViewInit, OnDestroy, OnChanges {
/** Label for the filter field (e.g. "Filter by"). Will be placed next to the filter icon. */
@Input() label = '';
diff --git a/libs/examples/src/examples.module.ts b/libs/examples/src/examples.module.ts
index 46fd9a7118..d1ffc2f12d 100644
--- a/libs/examples/src/examples.module.ts
+++ b/libs/examples/src/examples.module.ts
@@ -60,6 +60,7 @@ import { DtOverlayExamplesModule } from './overlay/overlay-examples.module';
import { DtPaginationExamplesModule } from './pagination/pagination-examples.module';
import { DtProgressBarExamplesModule } from './progress-bar/progress-bar-examples.module';
import { DtProgressCircleExamplesModule } from './progress-circle/progress-circle-examples.module';
+import { DtQuickFilterExamplesModule } from './quick-filter/quick-filter-examples.module';
import { DtRadialChartExamplesModule } from './radial-chart/radial-chart-examples.module';
import { DtRadioExamplesModule } from './radio/radio-examples.module';
import { DtExamplesSecondaryNAvModule } from './secondary-nav/secondary-nav-examples.module';
@@ -122,6 +123,7 @@ import { DtExamplesTreeTableModule } from './tree-table/tree-table-examples.modu
DtPaginationExamplesModule,
DtProgressBarExamplesModule,
DtProgressCircleExamplesModule,
+ DtQuickFilterExamplesModule,
DtRadialChartExamplesModule,
DtRadioExamplesModule,
DtExamplesSecondaryNAvModule,
diff --git a/libs/examples/src/index.ts b/libs/examples/src/index.ts
index 759773ae32..1645534b81 100644
--- a/libs/examples/src/index.ts
+++ b/libs/examples/src/index.ts
@@ -221,6 +221,7 @@ import { DtExampleProgressCircleDefault } from './progress-circle/progress-circl
import { DtExampleProgressCircleWithColor } from './progress-circle/progress-circle-with-color-example/progress-circle-with-color-example';
import { DtExampleProgressCircleWithIcon } from './progress-circle/progress-circle-with-icon-example/progress-circle-with-icon-example';
import { DtExampleProgressCircleWithText } from './progress-circle/progress-circle-with-text-example/progress-circle-with-text-example';
+import { DtExampleQuickFilterDefault } from './quick-filter/quick-filter-default-example/quick-filter-default-example';
import { DtExampleRadialChartCustomColors } from './radial-chart/radial-chart-custom-colors-example/radial-chart-custom-colors-example';
import { DtExampleRadialChartDefaultDonut } from './radial-chart/radial-chart-default-donut-example/radial-chart-default-donut-example';
import { DtExampleRadialChartDefaultPie } from './radial-chart/radial-chart-default-pie-example/radial-chart-default-pie-example';
@@ -347,6 +348,7 @@ export { DtOverlayExamplesModule } from './overlay/overlay-examples.module';
export { DtPaginationExamplesModule } from './pagination/pagination-examples.module';
export { DtProgressBarExamplesModule } from './progress-bar/progress-bar-examples.module';
export { DtProgressCircleExamplesModule } from './progress-circle/progress-circle-examples.module';
+export { DtQuickFilterExamplesModule } from './quick-filter/quick-filter-examples.module';
export { DtRadialChartExamplesModule } from './radial-chart/radial-chart-examples.module';
export { DtRadioExamplesModule } from './radio/radio-examples.module';
export { DtExamplesSecondaryNAvModule } from './secondary-nav/secondary-nav-examples.module';
@@ -562,6 +564,7 @@ export {
DtExampleProgressCircleWithColor,
DtExampleProgressCircleWithIcon,
DtExampleProgressCircleWithText,
+ DtExampleQuickFilterDefault,
DtExampleRadialChartCustomColors,
DtExampleRadialChartDefaultDonut,
DtExampleRadialChartDefaultPie,
@@ -895,6 +898,7 @@ export const EXAMPLES_MAP = new Map>([
['DtExampleProgressCircleWithColor', DtExampleProgressCircleWithColor],
['DtExampleProgressCircleWithIcon', DtExampleProgressCircleWithIcon],
['DtExampleProgressCircleWithText', DtExampleProgressCircleWithText],
+ ['DtExampleQuickFilterDefault', DtExampleQuickFilterDefault],
['DtExampleRadialChartCustomColors', DtExampleRadialChartCustomColors],
['DtExampleRadialChartDefaultDonut', DtExampleRadialChartDefaultDonut],
['DtExampleRadialChartDefaultPie', DtExampleRadialChartDefaultPie],
diff --git a/libs/examples/src/quick-filter/quick-filter-default-example/quick-filter-default-example.html b/libs/examples/src/quick-filter/quick-filter-default-example/quick-filter-default-example.html
new file mode 100644
index 0000000000..771d132a42
--- /dev/null
+++ b/libs/examples/src/quick-filter/quick-filter-default-example/quick-filter-default-example.html
@@ -0,0 +1,13 @@
+
+ Quick-filter
+
+ All options in the filter field above
+
+
+ my content
+
diff --git a/libs/examples/src/quick-filter/quick-filter-default-example/quick-filter-default-example.ts b/libs/examples/src/quick-filter/quick-filter-default-example/quick-filter-default-example.ts
new file mode 100644
index 0000000000..5bb770a01f
--- /dev/null
+++ b/libs/examples/src/quick-filter/quick-filter-default-example/quick-filter-default-example.ts
@@ -0,0 +1,41 @@
+/**
+ * @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,
+} from '@dynatrace/barista-components/experimental/quick-filter';
+import { FILTER_FIELD_TEST_DATA as filterFieldData } from '@dynatrace/testing/fixtures';
+
+@Component({
+ selector: 'dt-example-quick-filter-default',
+ templateUrl: 'quick-filter-default-example.html',
+})
+export class DtExampleQuickFilterDefault {
+ /** 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 !== 'Not in Quickfilter',
+ };
+
+ _dataSource = new DtQuickFilterDefaultDataSource(
+ filterFieldData,
+ this._config,
+ );
+}
diff --git a/libs/examples/src/quick-filter/quick-filter-examples.module.ts b/libs/examples/src/quick-filter/quick-filter-examples.module.ts
new file mode 100644
index 0000000000..8c2417e457
--- /dev/null
+++ b/libs/examples/src/quick-filter/quick-filter-examples.module.ts
@@ -0,0 +1,28 @@
+/**
+ * @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 { NgModule } from '@angular/core';
+import { DtQuickFilterModule } from '@dynatrace/barista-components/experimental/quick-filter';
+import { DtExampleQuickFilterDefault } from './quick-filter-default-example/quick-filter-default-example';
+
+export const DT_QUICK_FILTER_EXAMPLES = [DtExampleQuickFilterDefault];
+
+@NgModule({
+ imports: [DtQuickFilterModule],
+ declarations: [...DT_QUICK_FILTER_EXAMPLES],
+ entryComponents: [...DT_QUICK_FILTER_EXAMPLES],
+})
+export class DtQuickFilterExamplesModule {}
diff --git a/libs/testing/fixtures/src/index.ts b/libs/testing/fixtures/src/index.ts
index f1795bf364..de85075447 100644
--- a/libs/testing/fixtures/src/index.ts
+++ b/libs/testing/fixtures/src/index.ts
@@ -15,3 +15,6 @@
*/
export * from './lib/chart';
+export * from './lib/filter-field/test-data';
+export * from './lib/filter-field/test-data-validators';
+export * from './lib/tree-table/test-data';
diff --git a/libs/testing/fixtures/src/lib/filter-field/test-data-validators.ts b/libs/testing/fixtures/src/lib/filter-field/test-data-validators.ts
new file mode 100644
index 0000000000..139661e641
--- /dev/null
+++ b/libs/testing/fixtures/src/lib/filter-field/test-data-validators.ts
@@ -0,0 +1,61 @@
+/**
+ * @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 { Validators } from '@angular/forms';
+
+export const FILTER_FIELD_TEST_DATA_VALIDATORS = {
+ autocomplete: [
+ {
+ name: 'custom normal',
+ suggestions: [],
+ },
+ {
+ name: 'custom required',
+ suggestions: [],
+ validators: [
+ { validatorFn: Validators.required, error: 'field is required' },
+ ],
+ },
+ {
+ name: 'custom with multiple',
+ suggestions: [],
+ validators: [
+ { validatorFn: Validators.required, error: 'field is required' },
+ { validatorFn: Validators.minLength(3), error: 'min 3 characters' },
+ ],
+ },
+ {
+ name: 'outer-option',
+ autocomplete: [
+ {
+ name: 'inner-option',
+ },
+ ],
+ },
+ {
+ name: 'Autocomplete with free text options',
+ autocomplete: [
+ { name: 'Autocomplete option 1' },
+ { name: 'Autocomplete option 2' },
+ { name: 'Autocomplete option 3' },
+ {
+ name: 'Autocomplete free text',
+ suggestions: ['Suggestion 1', 'Suggestion 2', 'Suggestion 3'],
+ validators: [],
+ },
+ ],
+ },
+ ],
+};
diff --git a/libs/testing/fixtures/src/lib/filter-field/test-data.ts b/libs/testing/fixtures/src/lib/filter-field/test-data.ts
new file mode 100644
index 0000000000..a75237b258
--- /dev/null
+++ b/libs/testing/fixtures/src/lib/filter-field/test-data.ts
@@ -0,0 +1,112 @@
+/**
+ * @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.
+ */
+
+export const FILTER_FIELD_TEST_DATA = {
+ autocomplete: [
+ {
+ name: 'AUT',
+ distinct: true,
+ autocomplete: [{ name: 'Linz' }, { name: 'Vienna' }, { name: 'Graz' }],
+ },
+ {
+ name: 'USA',
+ autocomplete: [
+ { name: 'San Francisco' },
+ { name: 'Los Angeles' },
+ { name: 'New York' },
+ { name: 'Custom', suggestions: [] },
+ ],
+ },
+ {
+ name: 'Requests per minute',
+ range: {
+ operators: {
+ range: true,
+ equal: true,
+ greaterThanEqual: true,
+ lessThanEqual: true,
+ },
+ },
+ unit: 's',
+ },
+ {
+ name: 'Not in Quickfilter',
+ autocomplete: [
+ { name: 'Option1' },
+ { name: 'Option2' },
+ { name: 'Option3' },
+ ],
+ },
+ ],
+};
+
+export const FILTER_FIELD_TEST_DATA_SINGLE_DISTINCT = {
+ autocomplete: [
+ {
+ name: 'AUT',
+ distinct: true,
+ autocomplete: [
+ {
+ name: 'Vienna',
+ },
+ {
+ name: 'Linz',
+ },
+ ],
+ },
+ ],
+};
+
+export const FILTER_FIELD_TEST_DATA_SINGLE_OPTION = {
+ autocomplete: [{ name: 'option' }],
+};
+
+export const FILTER_FIELD_TEST_DATA_ASYNC = {
+ autocomplete: [
+ {
+ name: 'AUT',
+ autocomplete: [
+ {
+ name: 'Upper Austria',
+ distinct: true,
+ autocomplete: [
+ {
+ name: 'Cities',
+ options: [{ name: 'Linz' }, { name: 'Wels' }, { name: 'Steyr' }],
+ },
+ ],
+ },
+ {
+ name: 'Vienna',
+ },
+ ],
+ },
+ {
+ name: 'USA',
+ autocomplete: [{ name: 'Los Angeles' }, { name: 'San Fran' }],
+ },
+ {
+ name: 'Free',
+ suggestions: [],
+ validators: [],
+ },
+ {
+ name: 'DE (async)',
+ async: true,
+ autocomplete: [{ name: 'Berlin' }],
+ },
+ ],
+};
diff --git a/libs/testing/fixtures/src/lib/tree-table/test-data.ts b/libs/testing/fixtures/src/lib/tree-table/test-data.ts
new file mode 100644
index 0000000000..713cc5fbec
--- /dev/null
+++ b/libs/testing/fixtures/src/lib/tree-table/test-data.ts
@@ -0,0 +1,121 @@
+/**
+ * @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 { DtIconType } from '@dynatrace/barista-icons';
+
+// tslint:disable: dt-document-public-fields
+
+export const TREE_TABLE_TEST_DATA: ThreadNode[] = [
+ {
+ name: 'hz.hzInstance_1_cluster.thread',
+ icon: 'airplane',
+ threadlevel: 'S0',
+ totalTimeConsumption: 150,
+ waiting: 123,
+ running: 20,
+ blocked: 0,
+ children: [
+ {
+ name:
+ 'hz.hzInstance_1_cluster.thread_1_hz.hzInstance_1_cluster.thread-1',
+ icon: 'airplane',
+ threadlevel: 'S1',
+ totalTimeConsumption: 150,
+ waiting: 123,
+ running: 20,
+ blocked: 0,
+ },
+ {
+ name: 'hz.hzInstance_1_cluster.thread-2',
+ icon: 'airplane',
+ threadlevel: 'S1',
+ totalTimeConsumption: 150,
+ waiting: 130,
+ running: 0,
+ blocked: 0,
+ },
+ ],
+ },
+ {
+ name: 'jetty',
+ icon: 'airplane',
+ threadlevel: 'S0',
+ totalTimeConsumption: 150,
+ waiting: 123,
+ running: 20,
+ blocked: 0,
+ children: [
+ {
+ name: 'jetty-422',
+ icon: 'airplane',
+ threadlevel: 'S1',
+ totalTimeConsumption: 150,
+ waiting: 123,
+ running: 20,
+ blocked: 0,
+ },
+ {
+ name: 'jetty-423',
+ icon: 'airplane',
+ threadlevel: 'S1',
+ totalTimeConsumption: 150,
+ waiting: 130,
+ running: 0,
+ blocked: 0,
+ },
+ {
+ name: 'jetty-424',
+ icon: 'airplane',
+ threadlevel: 'S1',
+ totalTimeConsumption: 150,
+ waiting: 130,
+ running: 0,
+ blocked: 0,
+ },
+ ],
+ },
+ {
+ name: 'Downtime timer',
+ icon: 'airplane',
+ threadlevel: 'S0',
+ totalTimeConsumption: 150,
+ waiting: 123,
+ running: 20,
+ blocked: 0,
+ },
+];
+
+export class ThreadNode {
+ name: string;
+ threadlevel: string;
+ totalTimeConsumption: number;
+ blocked: number;
+ running: number;
+ waiting: number;
+ icon: DtIconType;
+ children?: ThreadNode[];
+}
+
+export class ThreadFlatNode {
+ name: string;
+ threadlevel: string;
+ totalTimeConsumption: number;
+ blocked: number;
+ running: number;
+ waiting: number;
+ icon: DtIconType;
+ level: number;
+ expandable: boolean;
+}
diff --git a/nx.json b/nx.json
index f26dd02371..02f1d1aeac 100644
--- a/nx.json
+++ b/nx.json
@@ -242,6 +242,9 @@
"progress-circle": {
"tags": ["scope:components", "type:library"]
},
+ "quick-filter": {
+ "tags": ["scope:components", "type:library"]
+ },
"radial-chart": {
"tags": ["scope:components", "type:library"]
},
diff --git a/tsconfig.json b/tsconfig.json
index 0bff269c2a..86dc245651 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -86,6 +86,9 @@
"@dynatrace/barista-components/expandable-text": [
"libs/barista-components/expandable-text/index.ts"
],
+ "@dynatrace/barista-components/experimental/quick-filter": [
+ "libs/barista-components/experimental/quick-filter/index.ts"
+ ],
"@dynatrace/barista-components/filter-field": [
"libs/barista-components/filter-field/index.ts"
],
diff --git a/tslint.json b/tslint.json
index 4b948bd320..c60546feb5 100644
--- a/tslint.json
+++ b/tslint.json
@@ -76,9 +76,13 @@
"sourceTag": "type:app",
"onlyDependOnLibsWithTags": ["type:library"]
},
+ {
+ "sourceTag": "type:e2e",
+ "onlyDependOnLibsWithTags": ["type:library"]
+ },
{
"sourceTag": "scope:examples",
- "onlyDependOnLibsWithTags": ["scope:components"]
+ "onlyDependOnLibsWithTags": ["scope:components", "scope:testing"]
}
]
}