From 9388ff7b43d5743dfc8933690f1c67a7befef69d Mon Sep 17 00:00:00 2001
From: Joe Reuter <johannes.reuter@elastic.co>
Date: Mon, 17 Feb 2020 13:52:53 +0100
Subject: [PATCH] Fix auto refresh in visualizations and lens (#57667)

---
 .../visualize/np_ready/editor/editor.js       |  8 -----
 .../public/embeddable/visualize_embeddable.ts |  8 +++++
 .../visualize_embeddable_factory.tsx          |  7 +++-
 .../public/np_ready/public/mocks.ts           |  1 +
 .../public/np_ready/public/plugin.ts          | 13 +++++--
 .../timefilter/timefilter_service.mock.ts     |  3 +-
 .../embeddable/embeddable.test.tsx            | 35 ++++++++++++++++++-
 .../embeddable/embeddable.tsx                 | 15 +++++++-
 .../embeddable/embeddable_factory.ts          |  8 ++++-
 .../public/editor_frame_service/mocks.tsx     |  9 ++---
 .../public/editor_frame_service/service.tsx   |  1 +
 11 files changed, 86 insertions(+), 22 deletions(-)

diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
index 27fb9b63843c4..657104344662f 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js
@@ -441,14 +441,6 @@ function VisualizeAppController(
       })
     );
 
-    subscriptions.add(
-      subscribeWithScope($scope, timefilter.getAutoRefreshFetch$(), {
-        next: () => {
-          $scope.vis.forceReload();
-        },
-      })
-    );
-
     $scope.$on('$destroy', () => {
       if ($scope._handler) {
         $scope._handler.destroy();
diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts
index 5e593398333c9..fddcf70c30605 100644
--- a/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts
+++ b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts
@@ -34,6 +34,7 @@ import {
   esFilters,
   Filter,
   ISearchSource,
+  TimefilterContract,
 } from '../../../../../plugins/data/public';
 import {
   EmbeddableInput,
@@ -106,8 +107,10 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
   private vis: Vis;
   private domNode: any;
   public readonly type = VISUALIZE_EMBEDDABLE_TYPE;
+  private autoRefreshFetchSubscription: Subscription;
 
   constructor(
+    timefilter: TimefilterContract,
     {
       savedVisualization,
       editUrl,
@@ -151,6 +154,10 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
 
     this.vis._setUiState(this.uiState);
 
+    this.autoRefreshFetchSubscription = timefilter
+      .getAutoRefreshFetch$()
+      .subscribe(this.updateHandler.bind(this));
+
     this.subscriptions.push(
       Rx.merge(this.getOutput$(), this.getInput$()).subscribe(() => {
         this.handleChanges();
@@ -345,6 +352,7 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
       this.handler.destroy();
       this.handler.getElement().remove();
     }
+    this.autoRefreshFetchSubscription.unsubscribe();
   }
 
   public reload = () => {
diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
index 03471174753fa..2f00467a85cda 100644
--- a/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
+++ b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
@@ -38,6 +38,7 @@ import { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
 
 import { getCapabilities, getHttp, getTypes, getUISettings } from '../np_ready/public/services';
 import { showNewVisModal } from '../np_ready/public/wizard';
+import { TimefilterContract } from '../../../../../plugins/data/public';
 
 interface VisualizationAttributes extends SavedObjectAttributes {
   visState: string;
@@ -51,7 +52,10 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
 > {
   public readonly type = VISUALIZE_EMBEDDABLE_TYPE;
 
-  constructor(private getSavedVisualizationsLoader: () => SavedVisualizations) {
+  constructor(
+    private timefilter: TimefilterContract,
+    private getSavedVisualizationsLoader: () => SavedVisualizations
+  ) {
     super({
       savedObjectMetaData: {
         name: i18n.translate('visualizations.savedObjectName', { defaultMessage: 'Visualization' }),
@@ -114,6 +118,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
       const indexPattern = await getIndexPattern(savedObject);
       const indexPatterns = indexPattern ? [indexPattern] : [];
       return new VisualizeEmbeddable(
+        this.timefilter,
         {
           savedVisualization: savedObject,
           indexPatterns,
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts
index a948757d7bd83..9fb87cadb2983 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts
@@ -56,6 +56,7 @@ const createInstance = async () => {
   const plugin = new VisualizationsPlugin({} as PluginInitializerContext);
 
   const setup = plugin.setup(coreMock.createSetup(), {
+    data: dataPluginMock.createSetupContract(),
     expressions: expressionsPluginMock.createSetupContract(),
     embeddable: embeddablePluginMock.createStartContract(),
     usageCollection: usageCollectionPluginMock.createSetupContract(),
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts
index 36c04923e3fd0..20bed59faad88 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts
@@ -36,7 +36,10 @@ import { ExpressionsSetup } from '../../../../../../plugins/expressions/public';
 import { IEmbeddableSetup } from '../../../../../../plugins/embeddable/public';
 import { visualization as visualizationFunction } from './expressions/visualization_function';
 import { visualization as visualizationRenderer } from './expressions/visualization_renderer';
-import { DataPublicPluginStart } from '../../../../../../plugins/data/public';
+import {
+  DataPublicPluginSetup,
+  DataPublicPluginStart,
+} from '../../../../../../plugins/data/public';
 import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public';
 import {
   createSavedVisLoader,
@@ -65,6 +68,7 @@ export interface VisualizationsSetupDeps {
   expressions: ExpressionsSetup;
   embeddable: IEmbeddableSetup;
   usageCollection: UsageCollectionSetup;
+  data: DataPublicPluginSetup;
 }
 
 export interface VisualizationsStartDeps {
@@ -95,7 +99,7 @@ export class VisualizationsPlugin
 
   public setup(
     core: CoreSetup,
-    { expressions, embeddable, usageCollection }: VisualizationsSetupDeps
+    { expressions, embeddable, usageCollection, data }: VisualizationsSetupDeps
   ): VisualizationsSetup {
     setUISettings(core.uiSettings);
     setUsageCollector(usageCollection);
@@ -103,7 +107,10 @@ export class VisualizationsPlugin
     expressions.registerFunction(visualizationFunction);
     expressions.registerRenderer(visualizationRenderer);
 
-    const embeddableFactory = new VisualizeEmbeddableFactory(this.getSavedVisualizationsLoader);
+    const embeddableFactory = new VisualizeEmbeddableFactory(
+      data.query.timefilter.timefilter,
+      this.getSavedVisualizationsLoader
+    );
     embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory);
 
     return {
diff --git a/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts b/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts
index 2923cee60f898..80c13464ad98a 100644
--- a/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts
+++ b/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts
@@ -18,6 +18,7 @@
  */
 
 import { TimefilterService, TimeHistoryContract, TimefilterContract } from '.';
+import { Observable } from 'rxjs';
 
 export type TimefilterServiceClientContract = PublicMethodsOf<TimefilterService>;
 
@@ -28,7 +29,7 @@ const createSetupContractMock = () => {
     getEnabledUpdated$: jest.fn(),
     getTimeUpdate$: jest.fn(),
     getRefreshIntervalUpdate$: jest.fn(),
-    getAutoRefreshFetch$: jest.fn(),
+    getAutoRefreshFetch$: jest.fn(() => new Observable<unknown>()),
     getFetch$: jest.fn(),
     getTime: jest.fn(),
     setTime: jest.fn(),
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
index a07bd475cdfcb..55363ebe4d8f3 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
@@ -4,10 +4,12 @@
  * you may not use this file except in compliance with the Elastic License.
  */
 
+import { Subject } from 'rxjs';
 import { Embeddable } from './embeddable';
 import { ReactExpressionRendererProps } from 'src/plugins/expressions/public';
-import { Query, TimeRange, Filter } from 'src/plugins/data/public';
+import { Query, TimeRange, Filter, TimefilterContract } from 'src/plugins/data/public';
 import { Document } from '../../persistence';
+import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks';
 
 jest.mock('../../../../../../../src/plugins/inspector/public/', () => ({
   isAvailable: false,
@@ -44,6 +46,7 @@ describe('embeddable', () => {
 
   it('should render expression with expression renderer', () => {
     const embeddable = new Embeddable(
+      dataPluginMock.createSetupContract().query.timefilter.timefilter,
       expressionRenderer,
       {
         editUrl: '',
@@ -64,6 +67,7 @@ describe('embeddable', () => {
     const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: false } }];
 
     const embeddable = new Embeddable(
+      dataPluginMock.createSetupContract().query.timefilter.timefilter,
       expressionRenderer,
       {
         editUrl: '',
@@ -89,6 +93,7 @@ describe('embeddable', () => {
     const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: false } }];
 
     const embeddable = new Embeddable(
+      dataPluginMock.createSetupContract().query.timefilter.timefilter,
       expressionRenderer,
       {
         editUrl: '',
@@ -112,6 +117,7 @@ describe('embeddable', () => {
     const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: true } }];
 
     const embeddable = new Embeddable(
+      dataPluginMock.createSetupContract().query.timefilter.timefilter,
       expressionRenderer,
       {
         editUrl: '',
@@ -130,4 +136,31 @@ describe('embeddable', () => {
 
     expect(expressionRenderer).toHaveBeenCalledTimes(1);
   });
+
+  it('should re-render on auto refresh fetch observable', () => {
+    const timeRange: TimeRange = { from: 'now-15d', to: 'now' };
+    const query: Query = { language: 'kquery', query: '' };
+    const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: true } }];
+
+    const autoRefreshFetchSubject = new Subject();
+    const timefilter = ({
+      getAutoRefreshFetch$: () => autoRefreshFetchSubject.asObservable(),
+    } as unknown) as TimefilterContract;
+
+    const embeddable = new Embeddable(
+      timefilter,
+      expressionRenderer,
+      {
+        editUrl: '',
+        editable: true,
+        savedVis,
+      },
+      { id: '123', timeRange, query, filters }
+    );
+    embeddable.render(mountpoint);
+
+    autoRefreshFetchSubject.next();
+
+    expect(expressionRenderer).toHaveBeenCalledTimes(2);
+  });
 });
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
index a3a55f26ff7c2..252ba5c9bc0bc 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
@@ -7,7 +7,13 @@
 import _ from 'lodash';
 import React from 'react';
 import { render, unmountComponentAtNode } from 'react-dom';
-import { Query, TimeRange, Filter, IIndexPattern } from 'src/plugins/data/public';
+import {
+  Query,
+  TimeRange,
+  Filter,
+  IIndexPattern,
+  TimefilterContract,
+} from 'src/plugins/data/public';
 import { Subscription } from 'rxjs';
 import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public';
 import {
@@ -43,6 +49,7 @@ export class Embeddable extends AbstractEmbeddable<LensEmbeddableInput, LensEmbe
   private savedVis: Document;
   private domNode: HTMLElement | Element | undefined;
   private subscription: Subscription;
+  private autoRefreshFetchSubscription: Subscription;
 
   private currentContext: {
     timeRange?: TimeRange;
@@ -52,6 +59,7 @@ export class Embeddable extends AbstractEmbeddable<LensEmbeddableInput, LensEmbe
   } = {};
 
   constructor(
+    timefilter: TimefilterContract,
     expressionRenderer: ReactExpressionRendererType,
     { savedVis, editUrl, editable, indexPatterns }: LensEmbeddableConfiguration,
     initialInput: LensEmbeddableInput,
@@ -76,6 +84,10 @@ export class Embeddable extends AbstractEmbeddable<LensEmbeddableInput, LensEmbe
     this.savedVis = savedVis;
     this.subscription = this.getInput$().subscribe(input => this.onContainerStateChanged(input));
     this.onContainerStateChanged(initialInput);
+
+    this.autoRefreshFetchSubscription = timefilter
+      .getAutoRefreshFetch$()
+      .subscribe(this.reload.bind(this));
   }
 
   onContainerStateChanged(containerState: LensEmbeddableInput) {
@@ -125,6 +137,7 @@ export class Embeddable extends AbstractEmbeddable<LensEmbeddableInput, LensEmbe
     if (this.subscription) {
       this.subscription.unsubscribe();
     }
+    this.autoRefreshFetchSubscription.unsubscribe();
   }
 
   reload() {
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
index e8bb8914fa292..d30ad62b385c2 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
@@ -11,7 +11,11 @@ import {
   SavedObjectsClientContract,
 } from 'kibana/public';
 import { i18n } from '@kbn/i18n';
-import { IndexPatternsContract, IndexPattern } from '../../../../../../../src/plugins/data/public';
+import {
+  IndexPatternsContract,
+  IndexPattern,
+  TimefilterContract,
+} from '../../../../../../../src/plugins/data/public';
 import { ReactExpressionRendererType } from '../../../../../../../src/plugins/expressions/public';
 import {
   EmbeddableFactory as AbstractEmbeddableFactory,
@@ -27,6 +31,7 @@ export class EmbeddableFactory extends AbstractEmbeddableFactory {
   type = DOC_TYPE;
 
   constructor(
+    private timefilter: TimefilterContract,
     private coreHttp: HttpSetup,
     private capabilities: RecursiveReadonly<Capabilities>,
     private savedObjectsClient: SavedObjectsClientContract,
@@ -85,6 +90,7 @@ export class EmbeddableFactory extends AbstractEmbeddableFactory {
     );
 
     return new Embeddable(
+      this.timefilter,
       this.expressionRenderer,
       {
         savedVis,
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/mocks.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/mocks.tsx
index cd121a1f96a2b..e606c69c8c386 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_service/mocks.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/mocks.tsx
@@ -14,6 +14,7 @@ import { embeddablePluginMock } from '../../../../../../src/plugins/embeddable/p
 import { expressionsPluginMock } from '../../../../../../src/plugins/expressions/public/mocks';
 import { DatasourcePublicAPI, FramePublicAPI, Datasource, Visualization } from '../types';
 import { EditorFrameSetupPlugins, EditorFrameStartPlugins } from './service';
+import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks';
 
 export function createMockVisualization(): jest.Mocked<Visualization> {
   return {
@@ -103,7 +104,7 @@ export function createExpressionRendererMock(): jest.Mock<
 
 export function createMockSetupDependencies() {
   return ({
-    data: {},
+    data: dataPluginMock.createSetupContract(),
     embeddable: embeddablePluginMock.createSetupContract(),
     expressions: expressionsPluginMock.createSetupContract(),
   } as unknown) as MockedSetupDependencies;
@@ -111,11 +112,7 @@ export function createMockSetupDependencies() {
 
 export function createMockStartDependencies() {
   return ({
-    data: {
-      indexPatterns: {
-        indexPatterns: {},
-      },
-    },
+    data: dataPluginMock.createSetupContract(),
     embeddable: embeddablePluginMock.createStartContract(),
     expressions: expressionsPluginMock.createStartContract(),
   } as unknown) as MockedStartDependencies;
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.tsx
index 9a3d724705a1a..7a0bb3a2cc50f 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.tsx
@@ -79,6 +79,7 @@ export class EditorFrameService {
     plugins.embeddable.registerEmbeddableFactory(
       'lens',
       new EmbeddableFactory(
+        plugins.data.query.timefilter.timefilter,
         core.http,
         core.application.capabilities,
         core.savedObjects.client,