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