mirror of https://github.com/grafana/grafana
Frontend: Reload the browser when backend configuration/assets change (#79057)
* Detect frontend asset changes * Update * merge main * Frontend: Detect new assets / versions / config changes (#79258) * avoid first check * Updates and add tests * Update * Update * Updated code * refine * use context --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com>pull/80010/head
parent
7210e378b8
commit
e924627659
@ -0,0 +1,49 @@ |
||||
import { locationService, setBackendSrv, BackendSrv } from '@grafana/runtime'; |
||||
|
||||
import { NewFrontendAssetsChecker } from './NewFrontendAssetsChecker'; |
||||
|
||||
describe('NewFrontendAssetsChecker', () => { |
||||
const backendApiGet = jest.fn().mockReturnValue(Promise.resolve({})); |
||||
const locationReload = jest.fn(); |
||||
|
||||
const originalLocation = window.location; |
||||
|
||||
beforeAll(() => { |
||||
Object.defineProperty(window, 'location', { |
||||
configurable: true, |
||||
value: { reload: locationReload }, |
||||
}); |
||||
}); |
||||
|
||||
afterAll(() => { |
||||
Object.defineProperty(window, 'location', { configurable: true, value: originalLocation }); |
||||
}); |
||||
|
||||
setBackendSrv({ |
||||
get: backendApiGet, |
||||
} as unknown as BackendSrv); |
||||
|
||||
it('Should skip update checks if below interval', () => { |
||||
const checker = new NewFrontendAssetsChecker(); |
||||
checker.start(); |
||||
|
||||
locationService.push('/d/123'); |
||||
|
||||
expect(backendApiGet).toHaveBeenCalledTimes(0); |
||||
}); |
||||
|
||||
it('Should do update check when changing dashboard or going home', async () => { |
||||
const checker = new NewFrontendAssetsChecker(0); |
||||
checker.start(); |
||||
|
||||
locationService.push('/d/asd'); |
||||
locationService.push('/d/other'); |
||||
locationService.push('/d/other?viewPanel=2'); |
||||
locationService.push('/ignored'); |
||||
locationService.push('/ignored?asd'); |
||||
locationService.push('/ignored/sub'); |
||||
locationService.push('/home'); |
||||
|
||||
expect(backendApiGet).toHaveBeenCalledTimes(2); |
||||
}); |
||||
}); |
@ -0,0 +1,112 @@ |
||||
import { Location } from 'history'; |
||||
import { isEqual } from 'lodash'; |
||||
|
||||
import { getBackendSrv, getGrafanaLiveSrv, locationService, reportInteraction } from '@grafana/runtime'; |
||||
|
||||
export class NewFrontendAssetsChecker { |
||||
private hasUpdates = false; |
||||
private previous?: FrontendAssetsAPIDTO; |
||||
private interval: number; |
||||
private checked = Date.now(); |
||||
private prevLocationPath = ''; |
||||
|
||||
public constructor(interval?: number) { |
||||
// Default to never check for updates if last check was 5 minutes ago
|
||||
this.interval = interval ?? 1000 * 60 * 5; |
||||
} |
||||
|
||||
public start() { |
||||
// Subscribe to live connection state changes and check for new assets when re-connected
|
||||
const live = getGrafanaLiveSrv(); |
||||
|
||||
if (live) { |
||||
live.getConnectionState().subscribe((connected) => { |
||||
if (connected) { |
||||
this._checkForUpdates(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
// Subscribe to location changes
|
||||
locationService.getHistory().listen(this.locationUpdated.bind(this)); |
||||
this.prevLocationPath = locationService.getLocation().pathname; |
||||
} |
||||
|
||||
/** |
||||
* Tries to detect some navigation events where it's safe to trigger a reload |
||||
*/ |
||||
private locationUpdated(location: Location) { |
||||
if (this.prevLocationPath === location.pathname) { |
||||
return; |
||||
} |
||||
|
||||
const newLocationSegments = location.pathname.split('/'); |
||||
|
||||
// We are going to home
|
||||
if (newLocationSegments[1] === '/' && this.prevLocationPath !== '/') { |
||||
this.reloadIfUpdateDetected(); |
||||
} |
||||
// Moving to dashboard (or changing dashboards)
|
||||
else if (newLocationSegments[1] === 'd') { |
||||
this.reloadIfUpdateDetected(); |
||||
} |
||||
// Track potential page change
|
||||
else if (this.hasUpdates) { |
||||
reportInteraction('new_frontend_assets_reload_ignored', { |
||||
newLocation: location.pathname, |
||||
prevLocation: this.prevLocationPath, |
||||
}); |
||||
} |
||||
|
||||
this.prevLocationPath = location.pathname; |
||||
} |
||||
|
||||
private async _checkForUpdates() { |
||||
if (this.hasUpdates) { |
||||
return; |
||||
} |
||||
|
||||
// Don't check too often
|
||||
if (Date.now() - this.checked < this.interval) { |
||||
return; |
||||
} |
||||
|
||||
this.checked = Date.now(); |
||||
|
||||
const previous = this.previous; |
||||
const result: FrontendAssetsAPIDTO = await getBackendSrv().get('/api/frontend/assets'); |
||||
|
||||
if (previous && !isEqual(previous, result)) { |
||||
this.hasUpdates = true; |
||||
|
||||
// Report that we detected new assets
|
||||
reportInteraction('new_frontend_assets_detected', { |
||||
assets: previous.assets !== result.assets, |
||||
plugins: previous.plugins !== result.plugins, |
||||
version: previous.version !== result.version, |
||||
flags: previous.flags !== result.flags, |
||||
}); |
||||
} |
||||
|
||||
this.previous = result; |
||||
} |
||||
|
||||
/** This is called on page navigation events */ |
||||
public reloadIfUpdateDetected() { |
||||
if (this.hasUpdates) { |
||||
// Report that we detected new assets
|
||||
reportInteraction('new_frontend_assets_reload', {}); |
||||
window.location.reload(); |
||||
} |
||||
|
||||
// Async check if the assets have changed
|
||||
this._checkForUpdates(); |
||||
} |
||||
} |
||||
|
||||
interface FrontendAssetsAPIDTO { |
||||
assets: string; |
||||
flags: string; |
||||
plugins: string; |
||||
version: string; |
||||
} |
Loading…
Reference in new issue