Live: Remove queryOverLive and live-service-web-worker experimental feature flags (#103518)
This commit is contained in:
@@ -2653,9 +2653,6 @@ exports[`better eslint`] = {
|
||||
"public/app/features/live/centrifuge/channel.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/live/centrifuge/serviceWorkerProxy.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/live/dashboard/DashboardChangedModal.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
||||
],
|
||||
|
||||
@@ -124,8 +124,6 @@ Experimental features might be changed or removed without prior notice.
|
||||
|
||||
| Feature toggle name | Description |
|
||||
| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `live-service-web-worker` | This will use a webworker thread to processes events rather than the main thread |
|
||||
| `queryOverLive` | Use Grafana Live WebSocket to execute backend queries |
|
||||
| `lokiExperimentalStreaming` | Support new streaming approach for loki (prototype, needs special loki build) |
|
||||
| `storage` | Configurable storage for dashboards, datasources, and resources |
|
||||
| `canvasPanelNesting` | Allow elements nesting |
|
||||
|
||||
@@ -24,14 +24,6 @@ export interface FeatureToggles {
|
||||
*/
|
||||
disableEnvelopeEncryption?: boolean;
|
||||
/**
|
||||
* This will use a webworker thread to processes events rather than the main thread
|
||||
*/
|
||||
['live-service-web-worker']?: boolean;
|
||||
/**
|
||||
* Use Grafana Live WebSocket to execute backend queries
|
||||
*/
|
||||
queryOverLive?: boolean;
|
||||
/**
|
||||
* Search for dashboards using panel title
|
||||
*/
|
||||
panelTitleSearch?: boolean;
|
||||
|
||||
@@ -72,15 +72,6 @@ export interface GrafanaLiveSrv {
|
||||
*/
|
||||
getDataStream(options: LiveDataStreamOptions): Observable<DataQueryResponse>;
|
||||
|
||||
/**
|
||||
* Execute a query over the live websocket and potentiall subscribe to a live channel.
|
||||
*
|
||||
* Since the initial request and subscription are on the same socket, this will support HA setups
|
||||
*
|
||||
* @alpha -- this function requires the feature toggle `queryOverLive` to be set
|
||||
*/
|
||||
getQueryData(options: LiveQueryDataOptions): Observable<DataQueryResponse>;
|
||||
|
||||
/**
|
||||
* For channels that support presence, this will request the current state from the server.
|
||||
*
|
||||
|
||||
@@ -196,13 +196,6 @@ class DataSourceWithBackend<
|
||||
to: range?.to.valueOf().toString(),
|
||||
};
|
||||
|
||||
if (config.featureToggles.queryOverLive) {
|
||||
return getGrafanaLiveSrv().getQueryData({
|
||||
request,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = request.headers ?? {};
|
||||
headers[PluginRequestHeaders.PluginID] = Array.from(pluginIDs).join(', ');
|
||||
headers[PluginRequestHeaders.DatasourceUID] = Array.from(dsUIDs).join(', ');
|
||||
|
||||
@@ -27,20 +27,6 @@ var (
|
||||
AllowSelfServe: false,
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "live-service-web-worker",
|
||||
Description: "This will use a webworker thread to processes events rather than the main thread",
|
||||
Stage: FeatureStageExperimental,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDashboardsSquad,
|
||||
},
|
||||
{
|
||||
Name: "queryOverLive",
|
||||
Description: "Use Grafana Live WebSocket to execute backend queries",
|
||||
Stage: FeatureStageExperimental,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDashboardsSquad,
|
||||
},
|
||||
{
|
||||
Name: "panelTitleSearch",
|
||||
Description: "Search for dashboards using panel title",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
Name,Stage,Owner,requiresDevMode,RequiresRestart,FrontendOnly
|
||||
disableEnvelopeEncryption,GA,@grafana/grafana-as-code,false,false,false
|
||||
live-service-web-worker,experimental,@grafana/dashboards-squad,false,false,true
|
||||
queryOverLive,experimental,@grafana/dashboards-squad,false,false,true
|
||||
panelTitleSearch,preview,@grafana/search-and-storage,false,false,false
|
||||
publicDashboardsEmailSharing,preview,@grafana/sharing-squad,false,false,false
|
||||
publicDashboardsScene,GA,@grafana/sharing-squad,false,false,true
|
||||
|
||||
|
@@ -11,14 +11,6 @@ const (
|
||||
// Disable envelope encryption (emergency only)
|
||||
FlagDisableEnvelopeEncryption = "disableEnvelopeEncryption"
|
||||
|
||||
// FlagLiveServiceWebWorker
|
||||
// This will use a webworker thread to processes events rather than the main thread
|
||||
FlagLiveServiceWebWorker = "live-service-web-worker"
|
||||
|
||||
// FlagQueryOverLive
|
||||
// Use Grafana Live WebSocket to execute backend queries
|
||||
FlagQueryOverLive = "queryOverLive"
|
||||
|
||||
// FlagPanelTitleSearch
|
||||
// Search for dashboards using panel title
|
||||
FlagPanelTitleSearch = "panelTitleSearch"
|
||||
|
||||
@@ -1726,7 +1726,8 @@
|
||||
"metadata": {
|
||||
"name": "live-service-web-worker",
|
||||
"resourceVersion": "1743693517832",
|
||||
"creationTimestamp": "2022-01-26T17:44:20Z"
|
||||
"creationTimestamp": "2022-01-26T17:44:20Z",
|
||||
"deletionTimestamp": "2025-04-07T09:56:17Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "This will use a webworker thread to processes events rather than the main thread",
|
||||
@@ -2588,7 +2589,8 @@
|
||||
"metadata": {
|
||||
"name": "queryOverLive",
|
||||
"resourceVersion": "1743693517832",
|
||||
"creationTimestamp": "2022-01-26T17:44:20Z"
|
||||
"creationTimestamp": "2022-01-26T17:44:20Z",
|
||||
"deletionTimestamp": "2025-04-07T09:56:17Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Use Grafana Live WebSocket to execute backend queries",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
|
||||
|
||||
export const createWorker = () => new Worker(new URL('./service.worker.ts', import.meta.url));
|
||||
@@ -1,33 +0,0 @@
|
||||
import * as comlink from 'comlink';
|
||||
import { from, Observable, switchMap } from 'rxjs';
|
||||
|
||||
export const remoteObservableAsObservable = <T>(remoteObs: comlink.RemoteObject<Observable<T>>): Observable<T> =>
|
||||
new Observable((subscriber) => {
|
||||
// Passing the callbacks as 3 separate arguments is deprecated, but it's the only option for now
|
||||
//
|
||||
// RxJS recreates the functions via `Function.bind` https://github.com/ReactiveX/rxjs/blob/62aca850a37f598b5db6085661e0594b81ec4281/src/internal/Subscriber.ts#L169
|
||||
// and thus erases the ProxyMarker created via comlink.proxy(fN) when the callbacks
|
||||
// are grouped together in a Observer object (ie. { next: (v) => ..., error: (err) => ..., complete: () => ... })
|
||||
//
|
||||
// solution: TBD (autoproxy all functions?)
|
||||
const remoteSubPromise = remoteObs.subscribe(
|
||||
comlink.proxy((nextValueInRemoteObs: T) => {
|
||||
subscriber.next(nextValueInRemoteObs);
|
||||
}),
|
||||
comlink.proxy((err: unknown) => {
|
||||
subscriber.error(err);
|
||||
}),
|
||||
comlink.proxy(() => {
|
||||
subscriber.complete();
|
||||
})
|
||||
);
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
remoteSubPromise.then((remoteSub) => remoteSub.unsubscribe());
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const promiseWithRemoteObservableAsObservable = <T>(
|
||||
promiseWithProxyObservable: Promise<comlink.RemoteObject<Observable<T>>>
|
||||
): Observable<T> => from(promiseWithProxyObservable).pipe(switchMap((val) => remoteObservableAsObservable(val)));
|
||||
@@ -1,64 +0,0 @@
|
||||
import './transferHandlers';
|
||||
|
||||
import * as comlink from 'comlink';
|
||||
|
||||
import { LiveChannelAddress } from '@grafana/data';
|
||||
import { LiveDataStreamOptions, LivePublishOptions, LiveQueryDataOptions } from '@grafana/runtime';
|
||||
|
||||
import { remoteObservableAsObservable } from './remoteObservable';
|
||||
import { CentrifugeService, CentrifugeSrvDeps } from './service';
|
||||
|
||||
let centrifuge: CentrifugeService;
|
||||
|
||||
const initialize = (
|
||||
deps: CentrifugeSrvDeps,
|
||||
remoteDataStreamSubscriberReadiness: comlink.RemoteObject<
|
||||
CentrifugeSrvDeps['dataStreamSubscriberReadiness'] & comlink.ProxyMarked
|
||||
>
|
||||
) => {
|
||||
centrifuge = new CentrifugeService({
|
||||
...deps,
|
||||
dataStreamSubscriberReadiness: remoteObservableAsObservable(remoteDataStreamSubscriberReadiness),
|
||||
});
|
||||
};
|
||||
|
||||
const getConnectionState = () => {
|
||||
return comlink.proxy(centrifuge.getConnectionState());
|
||||
};
|
||||
|
||||
const getDataStream = (options: LiveDataStreamOptions) => {
|
||||
return comlink.proxy(centrifuge.getDataStream(options));
|
||||
};
|
||||
|
||||
const getQueryData = async (options: LiveQueryDataOptions) => {
|
||||
return await centrifuge.getQueryData(options);
|
||||
};
|
||||
|
||||
const getStream = (address: LiveChannelAddress) => {
|
||||
return comlink.proxy(centrifuge.getStream(address));
|
||||
};
|
||||
|
||||
const getPresence = async (address: LiveChannelAddress) => {
|
||||
return await centrifuge.getPresence(address);
|
||||
};
|
||||
|
||||
const publish = (address: LiveChannelAddress, data: unknown, options?: LivePublishOptions) =>
|
||||
centrifuge.publish(address, data, options);
|
||||
|
||||
const workObj = {
|
||||
initialize,
|
||||
getConnectionState,
|
||||
getDataStream,
|
||||
getStream,
|
||||
getQueryData,
|
||||
getPresence,
|
||||
publish,
|
||||
};
|
||||
|
||||
export type RemoteCentrifugeService = typeof workObj;
|
||||
|
||||
comlink.expose(workObj);
|
||||
|
||||
export default class {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import './transferHandlers';
|
||||
|
||||
import * as comlink from 'comlink';
|
||||
import { asyncScheduler, Observable, observeOn } from 'rxjs';
|
||||
|
||||
import { LiveChannelAddress, LiveChannelEvent } from '@grafana/data';
|
||||
|
||||
import { createWorker } from './createCentrifugeServiceWorker';
|
||||
import { promiseWithRemoteObservableAsObservable } from './remoteObservable';
|
||||
import { CentrifugeSrv, CentrifugeSrvDeps } from './service';
|
||||
import { RemoteCentrifugeService } from './service.worker';
|
||||
|
||||
export class CentrifugeServiceWorkerProxy implements CentrifugeSrv {
|
||||
private centrifugeWorker;
|
||||
|
||||
constructor(deps: CentrifugeSrvDeps) {
|
||||
this.centrifugeWorker = comlink.wrap<RemoteCentrifugeService>(createWorker());
|
||||
this.centrifugeWorker.initialize(deps, comlink.proxy(deps.dataStreamSubscriberReadiness));
|
||||
}
|
||||
|
||||
getConnectionState: CentrifugeSrv['getConnectionState'] = () => {
|
||||
return promiseWithRemoteObservableAsObservable(this.centrifugeWorker.getConnectionState());
|
||||
};
|
||||
|
||||
getDataStream: CentrifugeSrv['getDataStream'] = (options) => {
|
||||
return promiseWithRemoteObservableAsObservable(this.centrifugeWorker.getDataStream(options)).pipe(
|
||||
// async scheduler splits the synchronous task of deserializing data from web worker and
|
||||
// consuming the message (ie. updating react component) into two to avoid blocking the event loop
|
||||
observeOn(asyncScheduler)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Query over websocket
|
||||
*/
|
||||
getQueryData: CentrifugeSrv['getQueryData'] = async (options) => {
|
||||
const optionsAsPlainSerializableObject = JSON.parse(JSON.stringify(options));
|
||||
return this.centrifugeWorker.getQueryData(optionsAsPlainSerializableObject);
|
||||
};
|
||||
|
||||
getPresence: CentrifugeSrv['getPresence'] = (address) => {
|
||||
return this.centrifugeWorker.getPresence(address);
|
||||
};
|
||||
|
||||
getStream: CentrifugeSrv['getStream'] = <T>(address: LiveChannelAddress) => {
|
||||
return promiseWithRemoteObservableAsObservable(
|
||||
this.centrifugeWorker.getStream(address) as Promise<comlink.Remote<Observable<LiveChannelEvent<T>>>>
|
||||
);
|
||||
};
|
||||
|
||||
publish: CentrifugeSrv['publish'] = (address, data, options) => {
|
||||
return this.centrifugeWorker.publish(address, data, options);
|
||||
};
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import * as comlink from 'comlink';
|
||||
import { Subscriber } from 'rxjs';
|
||||
|
||||
// Observers, ie. functions passed to `observable.subscribe(...)`, are converted to a subclass of `Subscriber` before they are sent to the source Observable.
|
||||
// The conversion happens internally in the RxJS library - this transfer handler is catches them and wraps them with a proxy
|
||||
const subscriberTransferHandler = {
|
||||
canHandle(value: unknown): value is Subscriber<unknown> {
|
||||
return Boolean(value && value instanceof Subscriber);
|
||||
},
|
||||
|
||||
serialize(value: Function): [MessagePort, Transferable[]] {
|
||||
const obj = comlink.proxy(value);
|
||||
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
|
||||
comlink.expose(obj, port1);
|
||||
|
||||
return [port2, [port2]];
|
||||
},
|
||||
|
||||
deserialize(value: MessagePort): comlink.Remote<MessagePort> {
|
||||
value.start();
|
||||
|
||||
return comlink.wrap<MessagePort>(value);
|
||||
},
|
||||
};
|
||||
comlink.transferHandlers.set('SubscriberHandler', subscriberTransferHandler);
|
||||
@@ -5,7 +5,6 @@ import { contextSrv } from '../../core/services/context_srv';
|
||||
import { loadUrlToken } from '../../core/utils/urlToken';
|
||||
|
||||
import { CentrifugeService } from './centrifuge/service';
|
||||
import { CentrifugeServiceWorkerProxy } from './centrifuge/serviceWorkerProxy';
|
||||
import { GrafanaLiveService } from './live';
|
||||
|
||||
export function initGrafanaLive() {
|
||||
@@ -18,9 +17,7 @@ export function initGrafanaLive() {
|
||||
grafanaAuthToken: loadUrlToken(),
|
||||
};
|
||||
|
||||
const centrifugeSrv = config.featureToggles['live-service-web-worker']
|
||||
? new CentrifugeServiceWorkerProxy(centrifugeServiceDeps)
|
||||
: new CentrifugeService(centrifugeServiceDeps);
|
||||
const centrifugeSrv = new CentrifugeService(centrifugeServiceDeps);
|
||||
|
||||
setGrafanaLiveSrv(
|
||||
new GrafanaLiveService({
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { from, map, of, switchMap } from 'rxjs';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { DataFrame, toLiveChannelId, StreamingDataFrame } from '@grafana/data';
|
||||
import { BackendSrv, GrafanaLiveSrv, toDataQueryResponse } from '@grafana/runtime';
|
||||
import { standardStreamOptionsProvider, toStreamingDataResponse } from '@grafana/runtime/internal';
|
||||
import { toLiveChannelId, StreamingDataFrame } from '@grafana/data';
|
||||
import { BackendSrv, GrafanaLiveSrv } from '@grafana/runtime';
|
||||
|
||||
import { CentrifugeSrv, StreamingDataQueryResponse } from './centrifuge/service';
|
||||
import { isStreamingResponseData, StreamingResponseDataType } from './data/utils';
|
||||
@@ -65,26 +64,6 @@ export class GrafanaLiveService implements GrafanaLiveSrv {
|
||||
return this.deps.centrifugeSrv.getStream(address);
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a query over the live websocket and potentially subscribe to a live channel.
|
||||
*
|
||||
* Since the initial request and subscription are on the same socket, this will support HA setups
|
||||
*/
|
||||
getQueryData: GrafanaLiveSrv['getQueryData'] = (options) => {
|
||||
return from(this.deps.centrifugeSrv.getQueryData(options)).pipe(
|
||||
switchMap((rawResponse) => {
|
||||
const parsedResponse = toDataQueryResponse(rawResponse, options.request.targets);
|
||||
|
||||
const isSubscribable =
|
||||
parsedResponse.data?.length && parsedResponse.data.find((f: DataFrame) => f.meta?.channel);
|
||||
|
||||
return isSubscribable
|
||||
? toStreamingDataResponse(parsedResponse, options.request, standardStreamOptionsProvider)
|
||||
: of(parsedResponse);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Publish into a channel
|
||||
*
|
||||
|
||||
@@ -25,12 +25,3 @@ class LayoutMockWorker {
|
||||
jest.mock('../../app/plugins/panel/nodeGraph/createLayoutWorker', () => ({
|
||||
createWorker: () => new LayoutMockWorker(),
|
||||
}));
|
||||
|
||||
class BasicMockWorker {
|
||||
postMessage() {}
|
||||
}
|
||||
const mockCreateWorker = {
|
||||
createWorker: () => new BasicMockWorker(),
|
||||
};
|
||||
|
||||
jest.mock('../../app/features/live/centrifuge/createCentrifugeServiceWorker', () => mockCreateWorker);
|
||||
|
||||
Reference in New Issue
Block a user