Switch the dashboard view to listening to the view model

Release-Notes: skip
Google-Bug-Id: b/244279450
Change-Id: Ib4d4a81c4249ba259d51064dba1c897e9e88038b
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
index 88bf943..3af856f 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -41,18 +41,22 @@
   firePageError,
   fireTitleChange,
 } from '../../../utils/event-util';
-import {GerritView} from '../../../services/router/router-model';
 import {RELOAD_DASHBOARD_INTERVAL_MS} from '../../../constants/constants';
 import {ChangeListSection} from '../gr-change-list/gr-change-list';
 import {a11yStyles} from '../../../styles/gr-a11y-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {LitElement, PropertyValues, html, css} from 'lit';
+import {LitElement, html, css, nothing} from 'lit';
 import {customElement, property, state, query} from 'lit/decorators.js';
 import {assertIsDefined} from '../../../utils/common-util';
 import {Shortcut} from '../../../services/shortcuts/shortcuts-config';
 import {ShortcutController} from '../../lit/shortcut-controller';
-import {DashboardViewState} from '../../../models/views/dashboard';
+import {
+  dashboardViewModelToken,
+  DashboardViewState,
+} from '../../../models/views/dashboard';
 import {createSearchUrl} from '../../../models/views/search';
+import {subscribe} from '../../lit/subscription-controller';
+import {resolve} from '../../../models/dependency';
 
 const PROJECT_PLACEHOLDER_PATTERN = /\${project}/g;
 
@@ -79,13 +83,13 @@
   @query('#confirmDeleteOverlay') protected confirmDeleteOverlay?: GrOverlay;
 
   @property({type: Object})
-  account: AccountDetailInfo | null = null;
+  account?: AccountDetailInfo;
 
   @property({type: Object})
   preferences?: PreferencesInput;
 
-  @property({type: Object})
-  params?: DashboardViewState;
+  @state()
+  viewState?: DashboardViewState;
 
   // private but used in test
   @state() results?: ChangeListSection[];
@@ -103,12 +107,29 @@
 
   private readonly restApiService = getAppContext().restApiService;
 
+  private readonly userModel = getAppContext().userModel;
+
+  private readonly getViewModel = resolve(this, dashboardViewModelToken);
+
   private lastVisibleTimestampMs = 0;
 
   private readonly shortcuts = new ShortcutController(this);
 
   constructor() {
     super();
+    subscribe(
+      this,
+      () => this.userModel.account$,
+      x => (this.account = x)
+    );
+    subscribe(
+      this,
+      () => this.getViewModel().state$,
+      x => {
+        this.viewState = x;
+        this.reload();
+      }
+    );
     this.addEventListener('reload', () => this.reload());
     this.shortcuts.addAbstract(Shortcut.UP_TO_DASHBOARD, () => this.reload());
   }
@@ -184,6 +205,7 @@
   }
 
   override render() {
+    if (!this.viewState) return nothing;
     return html`
       ${this.renderBanner()} ${this.renderContent()}
       <gr-overlay id="confirmDeleteOverlay" with-backdrop>
@@ -271,17 +293,15 @@
 
   private renderUserHeader() {
     if (
-      !this.params ||
-      this.params.view !== GerritView.DASHBOARD ||
-      !!this.params.project ||
-      !this.params.user ||
-      this.params.user === 'self'
+      !!this.viewState?.project ||
+      !this.viewState?.user ||
+      this.viewState?.user === 'self'
     ) {
       return;
     }
 
     return html`
-      <gr-user-header .userId=${this.params?.user}></gr-user-header>
+      <gr-user-header .userId=${this.viewState?.user}></gr-user-header>
     `;
   }
 
@@ -297,12 +317,6 @@
     `;
   }
 
-  override updated(changedProperties: PropertyValues) {
-    if (changedProperties.has('params')) {
-      this.paramsChanged();
-    }
-  }
-
   private loadPreferences() {
     return this.restApiService.getLoggedIn().then(loggedIn => {
       if (loggedIn) {
@@ -354,26 +368,15 @@
     return 'Dashboard for ' + user;
   }
 
-  private isViewActive(params: DashboardViewState) {
-    return params.view === GerritView.DASHBOARD;
-  }
-
-  // private but used in test
-  paramsChanged() {
-    return this.reload();
-  }
-
   /**
    * Reloads the element.
    *
    * private but used in test
    */
   reload() {
-    if (!this.params || !this.isViewActive(this.params)) {
-      return Promise.resolve();
-    }
+    if (!this.viewState) return Promise.resolve();
     this.loading = true;
-    const {project, dashboard, title, user, sections} = this.params;
+    const {project, dashboard, title, user, sections} = this.viewState;
     const dashboardPromise: Promise<UserDashboard | undefined> = project
       ? this.getProjectDashboard(project, dashboard)
       : Promise.resolve(
@@ -476,7 +479,7 @@
    * And then we want to emphasize the changes where the waiting time is larger.
    */
   private maybeSortResults(name: string, results: ChangeInfo[]) {
-    const userId = this.account && this.account._account_id;
+    const userId = this.account?._account_id;
     const sortedResults = [...results];
     if (name === YOUR_TURN.name && userId) {
       sortedResults.sort((c1, c2) => {
@@ -544,9 +547,7 @@
    */
   maybeShowDraftsBanner() {
     this.showDraftsBanner = false;
-    if (!(this.params?.user === 'self')) {
-      return;
-    }
+    if (!(this.viewState?.user === 'self')) return;
 
     if (!this.results) {
       throw new Error('this.results must be set. restAPI returned undefined');
@@ -555,16 +556,12 @@
     const draftSection = this.results.find(
       section => section.query === 'has:draft'
     );
-    if (!draftSection || !draftSection.results.length) {
-      return;
-    }
+    if (!draftSection || !draftSection.results.length) return;
 
     const closedChanges = draftSection.results.filter(
       change => !changeIsOpen(change)
     );
-    if (!closedChanges.length) {
-      return;
-    }
+    if (!closedChanges.length) return;
 
     this.showDraftsBanner = true;
   }
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
index 92afae1..7889e20 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
@@ -41,7 +41,6 @@
 suite('gr-dashboard-view tests', () => {
   let element: GrDashboardView;
 
-  let paramsChangedPromise: Promise<any>;
   let getChangesStub: SinonStubbedMember<
     RestApiService['getChangesForMultipleQueries']
   >;
@@ -54,30 +53,25 @@
         registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
       })
     );
-    stubRestApi('getAccountStatus').returns(Promise.resolve(undefined));
 
     element = await fixture<GrDashboardView>(html`
       <gr-dashboard-view></gr-dashboard-view>
     `);
 
     await element.updateComplete;
-    let resolver: (value?: any) => void;
-    paramsChangedPromise = new Promise(resolve => {
-      resolver = resolve;
-    });
-    const paramsChanged = element.paramsChanged.bind(element);
-    sinon
-      .stub(element, 'paramsChanged')
-      .callsFake(() => paramsChanged().then(() => resolver()));
   });
 
   test('render', async () => {
-    const sections = [
-      {name: 'test1', query: 'test1', hideIfEmpty: true},
-      {name: 'test2', query: 'test2', hideIfEmpty: true},
-    ];
+    element.viewState = {
+      view: GerritView.DASHBOARD,
+      user: 'self',
+      sections: [
+        {name: 'test1', query: 'test1', hideIfEmpty: true},
+        {name: 'test2', query: 'test2', hideIfEmpty: true},
+      ],
+    };
     getChangesStub.returns(Promise.resolve([[createChange()]]));
-    await element.fetchDashboardChanges({sections}, false);
+    await element.reload();
     element.loading = false;
     stubFlags('isEnabled').returns(true);
     element.requestUpdate();
@@ -125,14 +119,18 @@
 
   suite('bulk actions', () => {
     setup(async () => {
-      const sections = [
-        {name: 'test1', query: 'test1', hideIfEmpty: true},
-        {name: 'test2', query: 'test2', hideIfEmpty: true},
-      ];
+      element.viewState = {
+        view: GerritView.DASHBOARD,
+        user: 'user',
+        sections: [
+          {name: 'test1', query: 'test1', hideIfEmpty: true},
+          {name: 'test2', query: 'test2', hideIfEmpty: true},
+        ],
+      };
       getChangesStub.returns(Promise.resolve([[createChange()]]));
-      await element.fetchDashboardChanges({sections}, false);
-      element.loading = false;
       stubFlags('isEnabled').returns(true);
+      await element.reload();
+      element.loading = false;
       element.requestUpdate();
       await element.updateComplete;
     });
@@ -151,11 +149,6 @@
       getChangesStub.restore();
       getChangesStub.returns(Promise.resolve([[createChange()]]));
 
-      element.params = {
-        view: GerritView.DASHBOARD,
-        user: 'notself',
-        dashboard: '' as DashboardId,
-      };
       await element.reload();
       await element.updateComplete;
       assert.isTrue(checkbox.checked);
@@ -163,9 +156,20 @@
   });
 
   suite('drafts banner functionality', () => {
+    setup(async () => {
+      element.viewState = {
+        view: GerritView.DASHBOARD,
+        user: 'self',
+        sections: [
+          {name: 'test1', query: 'test1', hideIfEmpty: true},
+          {name: 'test2', query: 'test2', hideIfEmpty: true},
+        ],
+      };
+    });
+
     suite('maybeShowDraftsBanner', () => {
       test('not dashboard/self', () => {
-        element.params = {
+        element.viewState = {
           view: GerritView.DASHBOARD,
           user: 'notself',
           dashboard: '' as DashboardId,
@@ -176,7 +180,7 @@
 
       test('no drafts at all', () => {
         element.results = [];
-        element.params = {
+        element.viewState = {
           view: GerritView.DASHBOARD,
           user: 'self',
           dashboard: '' as DashboardId,
@@ -190,7 +194,7 @@
         element.results = [
           {countLabel: '', name: '', query: 'has:draft', results: [openChange]},
         ];
-        element.params = {
+        element.viewState = {
           view: GerritView.DASHBOARD,
           user: 'self',
           dashboard: '' as DashboardId,
@@ -210,7 +214,7 @@
           },
         ];
         assert.isFalse(changeIsOpen(element.results[0].results[0]));
-        element.params = {
+        element.viewState = {
           view: GerritView.DASHBOARD,
           user: 'self',
           dashboard: '' as DashboardId,
@@ -314,27 +318,10 @@
     });
   });
 
-  suite('_isViewActive', () => {
-    test('nothing happens when user param is falsy', async () => {
-      element.params = undefined;
-      await element.updateComplete;
-      assert.equal(getChangesStub.callCount, 0);
-    });
-
-    test('content is refreshed when user param is updated', async () => {
-      element.params = {
-        view: GerritView.DASHBOARD,
-        user: 'self',
-        dashboard: '' as DashboardId,
-      };
-      await paramsChangedPromise;
-      assert.isTrue(getChangesStub.called);
-    });
-  });
-
   suite('selfOnly sections', () => {
     test('viewing self dashboard includes selfOnly sections', async () => {
-      element.params = {
+      element.account = undefined;
+      element.viewState = {
         view: GerritView.DASHBOARD,
         user: 'self',
         dashboard: '' as DashboardId,
@@ -343,13 +330,13 @@
           {name: '', query: '2', selfOnly: true},
         ],
       };
-      await paramsChangedPromise;
+      await element.reload();
       assert.isTrue(getChangesStub.calledWith(undefined, ['1', '2']));
     });
 
     test('viewing dashboard when logged in includes owner:self query', async () => {
       element.account = createAccountDetailWithId(1);
-      element.params = {
+      element.viewState = {
         view: GerritView.DASHBOARD,
         user: 'self',
         dashboard: '' as DashboardId,
@@ -358,14 +345,14 @@
           {name: '', query: '2', selfOnly: true},
         ],
       };
-      await paramsChangedPromise;
+      await element.reload();
       assert.isTrue(
         getChangesStub.calledWith(undefined, ['1', '2', 'owner:self limit:1'])
       );
     });
 
     test("viewing another user's dashboard omits selfOnly sections", async () => {
-      element.params = {
+      element.viewState = {
         view: GerritView.DASHBOARD,
         user: 'user',
         dashboard: '' as DashboardId,
@@ -374,13 +361,13 @@
           {name: '', query: '2', selfOnly: true},
         ],
       };
-      await paramsChangedPromise;
+      await element.reload();
       assert.isTrue(getChangesStub.calledWith(undefined, ['1']));
     });
   });
 
   test('suffixForDashboard is included in getChanges query', async () => {
-    element.params = {
+    element.viewState = {
       view: GerritView.DASHBOARD,
       dashboard: '' as DashboardId,
       sections: [
@@ -388,7 +375,7 @@
         {name: '', query: '2', suffixForDashboard: 'suffix'},
       ],
     };
-    await paramsChangedPromise;
+    await element.reload();
     assert.isTrue(getChangesStub.calledWith(undefined, ['1', '2 suffix']));
   });
 
@@ -520,6 +507,9 @@
   });
 
   test('showNewUserHelp', async () => {
+    element.viewState = {
+      view: GerritView.DASHBOARD,
+    };
     element.loading = false;
     element.showNewUserHelp = false;
     await element.updateComplete;
@@ -547,11 +537,11 @@
   });
 
   test('gr-user-header', async () => {
-    element.params = undefined;
+    element.viewState = undefined;
     await element.updateComplete;
     assert.isNotOk(query(element, 'gr-user-header'));
 
-    element.params = {
+    element.viewState = {
       view: GerritView.DASHBOARD,
       dashboard: '' as DashboardId,
       user: 'self',
@@ -560,7 +550,7 @@
     assert.isNotOk(query(element, 'gr-user-header'));
 
     element.loading = false;
-    element.params = {
+    element.viewState = {
       view: GerritView.DASHBOARD,
       dashboard: '' as DashboardId,
       user: 'user',
@@ -568,7 +558,7 @@
     await element.updateComplete;
     assert.isOk(query(element, 'gr-user-header'));
 
-    element.params = {
+    element.viewState = {
       view: GerritView.DASHBOARD,
       dashboard: '' as DashboardId,
       project: 'p' as RepoName,
@@ -593,16 +583,16 @@
       assert.strictEqual((e as PageErrorEvent).detail.response, response);
       promise.resolve();
     });
-    element.params = {
+    element.viewState = {
       view: GerritView.DASHBOARD,
       dashboard: 'dashboard' as DashboardId,
       project: 'project' as RepoName,
       user: '',
     };
-    await Promise.all([paramsChangedPromise, promise]);
+    await Promise.all([element.reload(), promise]);
   });
 
-  test('params change triggers dashboardDisplayed()', async () => {
+  test('viewState change triggers dashboardDisplayed()', async () => {
     stubRestApi('getDashboard').returns(
       Promise.resolve({
         id: '' as DashboardId,
@@ -618,13 +608,13 @@
     );
     getChangesStub.returns(Promise.resolve([]));
     const dashboardDisplayedStub = stubReporting('dashboardDisplayed');
-    element.params = {
+    element.viewState = {
       view: GerritView.DASHBOARD,
       dashboard: 'dashboard' as DashboardId,
       project: 'project' as RepoName,
       user: '',
     };
-    await paramsChangedPromise;
+    await element.reload();
     assert.isTrue(dashboardDisplayedStub.calledOnce);
   });
 });
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index e8bc5bc..e4e9310 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -447,12 +447,7 @@
   private renderDashboardView() {
     return cache(
       this.view === GerritView.DASHBOARD
-        ? html`
-            <gr-dashboard-view
-              .account=${this.account}
-              .params=${this.params}
-            ></gr-dashboard-view>
-          `
+        ? html`<gr-dashboard-view></gr-dashboard-view>`
         : nothing
     );
   }
diff --git a/polygerrit-ui/app/models/views/dashboard.ts b/polygerrit-ui/app/models/views/dashboard.ts
index 0de6bf8..979debb 100644
--- a/polygerrit-ui/app/models/views/dashboard.ts
+++ b/polygerrit-ui/app/models/views/dashboard.ts
@@ -29,10 +29,6 @@
   title?: string;
 }
 
-const DEFAULT_STATE: DashboardViewState = {
-  view: GerritView.DASHBOARD,
-};
-
 const REPO_TOKEN_PATTERN = /\${(project|repo)}/g;
 
 function sectionsToEncodedParams(
@@ -73,9 +69,9 @@
   'dashboard-view-model'
 );
 
-export class DashboardViewModel extends Model<DashboardViewState> {
+export class DashboardViewModel extends Model<DashboardViewState | undefined> {
   constructor() {
-    super(DEFAULT_STATE);
+    super(undefined);
   }
 
   finalize() {}