diff --git a/package.json b/package.json index e0fd07bdcb9136..1ac592db333513 100644 --- a/package.json +++ b/package.json @@ -393,7 +393,7 @@ "chai": "3.5.0", "chance": "1.0.18", "cheerio": "0.22.0", - "chromedriver": "^80.0.1", + "chromedriver": "^81.0.0", "classnames": "2.2.6", "dedent": "^0.7.0", "delete-empty": "^2.0.0", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 807210a0dd18e3..14354bb2e8499e 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -4013,6 +4013,7 @@ function mkdirP (p, opts, f, made) { } switch (er.code) { case 'ENOENT': + if (path.dirname(p) === p) return cb(er); mkdirP(path.dirname(p), opts, function (er, made) { if (er) cb(er, made); else mkdirP(p, opts, cb, made); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index 2865f7d6694185..a6a0f48f08c196 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -148,6 +148,7 @@ export class DashboardAppController { }); // sync initial app filters from state to filterManager + // if there is an existing similar global filter, then leave it as global filterManager.setAppFilters(_.cloneDeep(dashboardStateManager.appState.filters)); // setup syncing of app filters between appState and filterManager const stopSyncingAppFilters = connectToQueryState( @@ -175,13 +176,16 @@ export class DashboardAppController { } // starts syncing `_g` portion of url with query services - // note: dashboard_state_manager.ts syncs `_a` portion of url // it is important to start this syncing after `dashboardStateManager.syncTimefilterWithDashboard(timefilter);` above is run, - // otherwise it will case redundant browser history record + // otherwise it will case redundant browser history records const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl( queryService, kbnUrlStateStorage ); + + // starts syncing `_a` portion of url + dashboardStateManager.startStateSyncing(); + $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; const getShouldShowEditHelp = () => diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts index 9b8f75bdcf9535..c1e3258029cf3b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts @@ -200,8 +200,10 @@ export class DashboardStateManager { }, stateStorage: this.kbnUrlStateStorage, }); + } - // actually start syncing state with container + public startStateSyncing() { + this.saveState({ replace: true }); this.stateSyncRef.start(); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts index dee279550aa6ad..c70e6dcb344e62 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts @@ -22,6 +22,7 @@ import { RefreshInterval, TimefilterContract } from 'src/plugins/data/public'; import { FilterUtils } from './filter_utils'; import { SavedObjectDashboard } from '../../../../../../../plugins/dashboard/public'; import { DashboardAppState } from '../types'; +import { esFilters } from '../../../../../../../plugins/data/public'; export function updateSavedDashboard( savedDashboard: SavedObjectDashboard, @@ -48,4 +49,9 @@ export function updateSavedDashboard( 'value', ]); savedDashboard.refreshInterval = savedDashboard.timeRestore ? timeRestoreObj : undefined; + // save only unpinned filters + const unpinnedFilters = savedDashboard + .getFilters() + .filter(filter => !esFilters.isFilterPinned(filter)); + savedDashboard.searchSource.setField('filter', unpinnedFilters); } diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.test.ts b/src/plugins/data/public/query/filter_manager/filter_manager.test.ts index ce574ace457053..3b4ca08cbbf14a 100644 --- a/src/plugins/data/public/query/filter_manager/filter_manager.test.ts +++ b/src/plugins/data/public/query/filter_manager/filter_manager.test.ts @@ -204,18 +204,19 @@ describe('filter_manager', () => { ).toBe(3); }); - test('should set app filters and remove any duplicated global filters', async function() { - filterManager.addFilters(readyFilters, true); + test('should set app filters and merge them with duplicate global filters', async function() { + const [filter, ...otherFilters] = readyFilters; + filterManager.addFilters(otherFilters, true); const appFilter1 = _.cloneDeep(readyFilters[1]); const appFilter2 = _.cloneDeep(readyFilters[2]); - filterManager.setAppFilters([appFilter1, appFilter2]); + filterManager.setAppFilters([filter, appFilter1, appFilter2]); const newGlobalFilters = filterManager.getGlobalFilters(); const newAppFilters = filterManager.getAppFilters(); - expect(newGlobalFilters).toHaveLength(1); - expect(newAppFilters).toHaveLength(2); + expect(newGlobalFilters).toHaveLength(2); + expect(newAppFilters).toHaveLength(1); }); test('should set global filters and remove any duplicated app filters', async function() { diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.ts b/src/plugins/data/public/query/filter_manager/filter_manager.ts index fba1866ebd6154..e206286bce1470 100644 --- a/src/plugins/data/public/query/filter_manager/filter_manager.ts +++ b/src/plugins/data/public/query/filter_manager/filter_manager.ts @@ -177,13 +177,9 @@ export class FilterManager { public setGlobalFilters(newGlobalFilters: Filter[]) { newGlobalFilters = mapAndFlattenFilters(newGlobalFilters); FilterManager.setFiltersStore(newGlobalFilters, FilterStateStore.GLOBAL_STATE, true); - const { appFilters: currentAppFilters } = this.getPartitionedFilters(); - // remove duplicates from current app filters, to make sure global will take precedence - const filteredAppFilters = currentAppFilters.filter( - appFilter => !newGlobalFilters.find(globalFilter => compareFilters(globalFilter, appFilter)) - ); + const { appFilters } = this.getPartitionedFilters(); const newFilters = this.mergeIncomingFilters({ - appFilters: filteredAppFilters, + appFilters, globalFilters: newGlobalFilters, }); @@ -198,14 +194,9 @@ export class FilterManager { public setAppFilters(newAppFilters: Filter[]) { newAppFilters = mapAndFlattenFilters(newAppFilters); FilterManager.setFiltersStore(newAppFilters, FilterStateStore.APP_STATE, true); - const { globalFilters: currentGlobalFilters } = this.getPartitionedFilters(); - // remove duplicates from current global filters, to make sure app will take precedence - const filteredGlobalFilters = currentGlobalFilters.filter( - globalFilter => !newAppFilters.find(appFilter => compareFilters(appFilter, globalFilter)) - ); - + const { globalFilters } = this.getPartitionedFilters(); const newFilters = this.mergeIncomingFilters({ - globalFilters: filteredGlobalFilters, + globalFilters, appFilters: newAppFilters, }); this.handleStateUpdate(newFilters); diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 6b5fd41dc06eab..528ec4800e7b90 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -88,6 +88,7 @@ class FilterItemUI extends Component { const dataTestSubjDisabled = `filter-${ this.props.filter.meta.disabled ? 'disabled' : 'enabled' }`; + const dataTestSubjPinned = `filter-${isFilterPinned(filter) ? 'pinned' : 'unpinned'}`; const classes = classNames( 'globalFilterItem', @@ -107,7 +108,7 @@ class FilterItemUI extends Component { className={classes} iconOnClick={() => this.props.onRemove()} onClick={this.handleBadgeClick} - data-test-subj={`filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue}`} + data-test-subj={`filter ${dataTestSubjDisabled} ${dataTestSubjKey} ${dataTestSubjValue} ${dataTestSubjPinned}`} /> ); diff --git a/test/functional/apps/dashboard/dashboard_filter_bar.js b/test/functional/apps/dashboard/dashboard_filter_bar.js index 6d2a30fa85325d..f6089871010c3f 100644 --- a/test/functional/apps/dashboard/dashboard_filter_bar.js +++ b/test/functional/apps/dashboard/dashboard_filter_bar.js @@ -27,6 +27,7 @@ export default function({ getService, getPageObjects }) { const pieChart = getService('pieChart'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); + const browser = getService('browser'); const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'visualize', 'timePicker']); describe('dashboard filter bar', () => { @@ -126,9 +127,46 @@ export default function({ getService, getPageObjects }) { const filterCount = await filterBar.getFilterCount(); expect(filterCount).to.equal(1); + await pieChart.expectPieSliceCount(1); + }); + it("restoring filters doesn't break back button", async () => { + await browser.goBack(); + await PageObjects.dashboard.expectExistsDashboardLandingPage(); + await browser.goForward(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); await pieChart.expectPieSliceCount(1); }); + + it("saving with pinned filter doesn't unpin them", async () => { + const filterKey = 'bytes'; + await filterBar.toggleFilterPinned(filterKey); + await PageObjects.dashboard.switchToEditMode(); + await PageObjects.dashboard.saveDashboard('saved with pinned filters', { + saveAsNew: true, + }); + expect(await filterBar.isFilterPinned(filterKey)).to.be(true); + await pieChart.expectPieSliceCount(1); + }); + + it("navigating to a dashboard with global filter doesn't unpin it if same filter is saved with dashboard", async () => { + await PageObjects.dashboard.preserveCrossAppState(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.loadSavedDashboard('with filters'); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await filterBar.isFilterPinned('bytes')).to.be(true); + await pieChart.expectPieSliceCount(1); + }); + + it("pinned filters aren't saved", async () => { + await filterBar.removeFilter('bytes'); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.loadSavedDashboard('saved with pinned filters'); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await filterBar.getFilterCount()).to.be(0); + await pieChart.expectPieSliceCount(5); + }); }); describe('saved search filtering', function() { diff --git a/test/functional/services/filter_bar.ts b/test/functional/services/filter_bar.ts index 9d494b1e6d950c..a463a593e9e049 100644 --- a/test/functional/services/filter_bar.ts +++ b/test/functional/services/filter_bar.ts @@ -32,10 +32,16 @@ export function FilterBarProvider({ getService, getPageObjects }: FtrProviderCon * @param value filter value * @param enabled filter status */ - public async hasFilter(key: string, value: string, enabled: boolean = true): Promise { + public async hasFilter( + key: string, + value: string, + enabled: boolean = true, + pinned: boolean = false + ): Promise { const filterActivationState = enabled ? 'enabled' : 'disabled'; + const filterPinnedState = pinned ? 'pinned' : 'unpinned'; return testSubjects.exists( - `filter filter-${filterActivationState} filter-key-${key} filter-value-${value}`, + `filter filter-${filterActivationState} filter-key-${key} filter-value-${value} filter-${filterPinnedState}`, { allowHidden: true, } @@ -80,6 +86,11 @@ export function FilterBarProvider({ getService, getPageObjects }: FtrProviderCon await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); } + public async isFilterPinned(key: string): Promise { + const filter = await testSubjects.find(`~filter & ~filter-key-${key}`); + return (await filter.getAttribute('data-test-subj')).includes('filter-pinned'); + } + public async getFilterCount(): Promise { const filters = await testSubjects.findAll('~filter'); return filters.length; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.tsx index a20bc7e21cfc5b..3aed1b7ac29537 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkPopover.tsx @@ -19,6 +19,7 @@ import { ManageCustomLink } from './ManageCustomLink'; import { px } from '../../../../style/variables'; const ScrollableContainer = styled.div` + -ms-overflow-style: none; max-height: ${px(535)}; overflow: scroll; `; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index e3fbcf8485d541..7ebfe26b83630c 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -124,7 +124,7 @@ export const TransactionActionMenu: FunctionComponent = ({ setIsActionPopoverOpen(true)} /> } > -
+
{isCustomLinksPopoverOpen ? ( { const { processorEvent, timeRange } = current; - const response = await search({ + const totalHitsResponse = await search({ index: indicesByProcessorEvent[processorEvent], body: { - size: 1, + size: 0, query: { bool: { filter: [ @@ -83,25 +83,43 @@ export const tasks: TelemetryTask[] = [ ] } }, - sort: { - '@timestamp': 'asc' - }, - _source: ['@timestamp'], track_total_hits: true } }); - const event = response.hits.hits[0]?._source as { - '@timestamp': number; - }; + const retainmentResponse = + timeRange === 'all' + ? await search({ + index: indicesByProcessorEvent[processorEvent], + body: { + query: { + bool: { + filter: [ + { term: { [PROCESSOR_EVENT]: processorEvent } } + ] + } + }, + sort: { + '@timestamp': 'asc' + }, + _source: ['@timestamp'] + } + }) + : null; + + const event = retainmentResponse?.hits.hits[0]?._source as + | { + '@timestamp': number; + } + | undefined; return merge({}, data, { counts: { [processorEvent]: { - [timeRange]: response.hits.total.value + [timeRange]: totalHitsResponse.hits.total.value } }, - ...(timeRange === 'all' && event + ...(event ? { retainment: { [processorEvent]: { diff --git a/yarn.lock b/yarn.lock index 40c616084b514f..7591db75b627c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6352,6 +6352,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" + integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== + dependencies: + "@types/node" "*" + "@types/zen-observable@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" @@ -10006,16 +10013,16 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^80.0.1: - version "80.0.1" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-80.0.1.tgz#35c1642e2d864b9e8262f291003e455b0e422917" - integrity sha512-VfRtZUpBUIjeypS+xM40+VD9g4Drv7L2VibG/4+0zX3mMx4KayN6gfKETycPfO6JwQXTLSxEr58fRcrsa8r5xQ== +chromedriver@^81.0.0: + version "81.0.0" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-81.0.0.tgz#690ba333aedf2b4c4933b6590c3242d3e5f28f3c" + integrity sha512-BA++IQ7O1FzHmNpzMlOfLiSBvPZ946uuhtJjZHEIr/Gb+Ha9jiuGbHiT45l6O3XGbQ8BAwvbmdisjl4rTxro4A== dependencies: "@testim/chrome-version" "^1.0.7" axios "^0.19.2" del "^5.1.0" - extract-zip "^1.6.7" - mkdirp "^1.0.3" + extract-zip "^2.0.0" + mkdirp "^1.0.4" tcp-port-used "^1.0.1" ci-info@^1.0.0: @@ -10652,7 +10659,7 @@ concat-stream@1.6.0, concat-stream@^1.4.7, concat-stream@~1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-stream@1.6.2, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@^1.6.1: +concat-stream@1.6.2, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -14410,7 +14417,7 @@ extract-zip@1.6.6: mkdirp "0.5.0" yauzl "2.4.1" -extract-zip@1.6.7, extract-zip@^1.6.6, extract-zip@^1.6.7: +extract-zip@1.6.7: version "1.6.7" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= @@ -14420,6 +14427,27 @@ extract-zip@1.6.7, extract-zip@^1.6.6, extract-zip@^1.6.7: mkdirp "0.5.1" yauzl "2.4.1" +extract-zip@^1.6.6, extract-zip@^1.6.7: + version "1.7.0" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" + integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== + dependencies: + concat-stream "^1.6.2" + debug "^2.6.9" + mkdirp "^0.5.4" + yauzl "^2.10.0" + +extract-zip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492" + integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -21814,7 +21842,7 @@ mkdirp@0.5.0: dependencies: minimist "0.0.8" -mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= @@ -21828,10 +21856,17 @@ mkdirp@0.5.3: dependencies: minimist "^1.2.5" -mkdirp@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea" - integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g== +mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@~0.5.0, mkdirp@~0.5.1: + version "0.5.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" + integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== + dependencies: + minimist "^1.2.5" + +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== mocha-junit-reporter@^1.23.1: version "1.23.1"