Chore: Clean up code from pluginsAPIMetrics feature toggle (#103965)

This commit is contained in:
Esteban Beltran
2025-04-14 13:15:06 +01:00
committed by GitHub
parent 2b279efe15
commit 20d8223b6e
7 changed files with 2 additions and 299 deletions
@@ -291,10 +291,6 @@ export interface FeatureToggles {
*/
externalCorePlugins?: boolean;
/**
* Sends metrics of public grafana packages usage by plugins
*/
pluginsAPIMetrics?: boolean;
/**
* Automatic service account and token setup for plugins
*/
externalServiceAccounts?: boolean;
-7
View File
@@ -481,13 +481,6 @@ var (
Owner: grafanaPluginsPlatformSquad,
Expression: "true", // enabled by default
},
{
Name: "pluginsAPIMetrics",
Description: "Sends metrics of public grafana packages usage by plugins",
FrontendOnly: true,
Stage: FeatureStageExperimental,
Owner: grafanaPluginsPlatformSquad,
},
{
Name: "externalServiceAccounts",
Description: "Automatic service account and token setup for plugins",
-1
View File
@@ -62,7 +62,6 @@ lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,false,false,
wargamesTesting,experimental,@grafana/hosted-grafana-team,false,false,false
alertingInsights,GA,@grafana/alerting-squad,false,false,true
externalCorePlugins,GA,@grafana/plugins-platform-backend,false,false,false
pluginsAPIMetrics,experimental,@grafana/plugins-platform-backend,false,false,true
externalServiceAccounts,preview,@grafana/identity-access-team,false,false,false
panelMonitoring,GA,@grafana/dataviz-squad,false,false,true
enableNativeHTTPHistogram,experimental,@grafana/grafana-backend-services-squad,false,true,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
62 wargamesTesting experimental @grafana/hosted-grafana-team false false false
63 alertingInsights GA @grafana/alerting-squad false false true
64 externalCorePlugins GA @grafana/plugins-platform-backend false false false
pluginsAPIMetrics experimental @grafana/plugins-platform-backend false false true
65 externalServiceAccounts preview @grafana/identity-access-team false false false
66 panelMonitoring GA @grafana/dataviz-squad false false true
67 enableNativeHTTPHistogram experimental @grafana/grafana-backend-services-squad false true false
-4
View File
@@ -259,10 +259,6 @@ const (
// Allow core plugins to be loaded as external
FlagExternalCorePlugins = "externalCorePlugins"
// FlagPluginsAPIMetrics
// Sends metrics of public grafana packages usage by plugins
FlagPluginsAPIMetrics = "pluginsAPIMetrics"
// FlagExternalServiceAccounts
// Automatic service account and token setup for plugins
FlagExternalServiceAccounts = "externalServiceAccounts"
+2 -1
View File
@@ -2438,7 +2438,8 @@
"metadata": {
"name": "pluginsAPIMetrics",
"resourceVersion": "1743693517832",
"creationTimestamp": "2023-09-21T11:36:32Z"
"creationTimestamp": "2023-09-21T11:36:32Z",
"deletionTimestamp": "2025-04-14T09:21:29Z"
},
"spec": {
"description": "Sends metrics of public grafana packages usage by plugins",
@@ -1,216 +0,0 @@
import { logInfo } from '@grafana/runtime';
import { trackPackageUsage } from './packageMetrics';
jest.mock('@grafana/runtime', () => ({
logInfo: jest.fn().mockImplementation(),
}));
// notice each test object has a different key to prevent hitting the cache
const logInfoMock = logInfo as jest.Mock;
const mockUsage = jest.fn();
describe('trackPackageUsage', () => {
beforeEach(() => {
logInfoMock.mockClear();
});
describe('With document.currentScript null', () => {
const originalCurrentScript = document.currentScript;
// set currentScript to null
beforeAll(() => {
Object.defineProperty(document, 'currentScript', {
value: null,
writable: true,
});
});
// restore original currentScript
afterAll(() => {
Object.defineProperty(document, 'currentScript', {
value: originalCurrentScript,
writable: true,
});
});
it('should not log API usage and for non-grafana packages', () => {
const obj = {
foo: 'bar',
};
const packageName = 'lodash';
const result = trackPackageUsage(obj, packageName);
mockUsage(result.foo);
expect(logInfoMock).toHaveBeenCalledTimes(0);
});
it('should log API usage and return a proxy object for @grafana/data packages', () => {
const obj = {
foo: 'bar',
};
const packageName = '@grafana/data';
const result = trackPackageUsage(obj, packageName);
mockUsage(result.foo);
expect(logInfoMock).toHaveBeenCalledTimes(1);
expect(logInfoMock).toHaveBeenLastCalledWith(`Plugin using @grafana/data.foo`, {
key: 'foo',
parent: '@grafana/data',
packageName: '@grafana/data',
});
expect(result).toEqual(obj);
});
it('should log API usage and return a proxy object for app/* packages', () => {
const obj = {
foo: 'bar',
};
const packageName = 'app/core/test';
const result = trackPackageUsage(obj, packageName);
mockUsage(result.foo);
expect(logInfoMock).toHaveBeenCalledTimes(1);
expect(logInfoMock).toHaveBeenLastCalledWith(`Plugin using app/core/test.foo`, {
key: 'foo',
parent: 'app/core/test',
packageName: 'app/core/test',
});
expect(result).toEqual(obj);
});
it('should return a proxy object for nested properties', () => {
const obj = {
foo2: {
bar: 'baz',
},
};
const packageName = '@grafana/data';
const result = trackPackageUsage(obj, packageName);
mockUsage(result.foo2.bar);
// 2 calls, one for each attribute
expect(logInfoMock).toHaveBeenCalledTimes(2);
expect(logInfoMock).toHaveBeenCalledWith(`Plugin using @grafana/data.foo2`, {
key: 'foo2',
parent: '@grafana/data',
packageName: '@grafana/data',
});
expect(logInfoMock).toHaveBeenCalledWith(`Plugin using @grafana/data.foo2.bar`, {
key: 'bar',
parent: '@grafana/data.foo2',
packageName: '@grafana/data',
});
expect(result.foo2).toEqual(obj.foo2);
});
it('should not log API usage for symbols or __useDefault key', () => {
const obj = {
[Symbol('key')]: 'value',
__useDefault: 'default',
};
const packageName = '@grafana/data';
const result = trackPackageUsage(obj, packageName);
expect(logInfoMock).not.toHaveBeenCalled();
expect(result).toEqual(obj);
});
it('should return the same proxy object for the same nested property', () => {
const obj = {
foo3: {
bar: 'baz',
},
};
const packageName = '@grafana/data';
const result1 = trackPackageUsage(obj, packageName);
const result2 = trackPackageUsage(obj, packageName);
mockUsage(result1.foo3);
expect(logInfoMock).toHaveBeenCalledTimes(1);
expect(logInfoMock).toHaveBeenCalledWith(`Plugin using @grafana/data.foo3`, {
key: 'foo3',
parent: '@grafana/data',
packageName: '@grafana/data',
});
mockUsage(result2.foo3.bar);
expect(logInfoMock).toHaveBeenCalledWith(`Plugin using @grafana/data.foo3.bar`, {
key: 'bar',
parent: '@grafana/data.foo3',
packageName: '@grafana/data',
});
expect(result1.foo3).toEqual(obj.foo3);
expect(result2.foo3).toEqual(obj.foo3);
expect(result1.foo3).toBe(result2.foo3);
});
it('should not report twice the same key usage', () => {
const obj = {
cacheMe: 'please',
zap: {
cacheMeInner: 'please',
},
};
const result = trackPackageUsage(obj, '@grafana/data');
mockUsage(result.cacheMe);
expect(logInfoMock).toHaveBeenCalledTimes(1);
mockUsage(result.cacheMe);
expect(logInfoMock).toHaveBeenCalledTimes(1);
mockUsage(result.zap);
expect(logInfoMock).toHaveBeenCalledTimes(2);
mockUsage(result.zap);
expect(logInfoMock).toHaveBeenCalledTimes(2);
mockUsage(result.zap.cacheMeInner);
expect(logInfoMock).toHaveBeenCalledTimes(3);
mockUsage(result.zap.cacheMeInner);
expect(logInfoMock).toHaveBeenCalledTimes(3);
expect(result).toEqual(obj);
});
});
it('Should skip tracking if document.currentScript is not null', () => {
// Save the original value of the attribute
const originalCurrentScript = document.currentScript;
// Define a new property on the document object with the mock currentScript
Object.defineProperty(document, 'currentScript', {
value: {
src: 'mocked-script.js',
},
writable: true,
});
const obj = {
lor: 'me',
};
const result = trackPackageUsage(obj, '@grafana/data');
mockUsage(result.lor);
expect(logInfoMock).not.toHaveBeenCalled();
// Restore the original value of the currentScript attribute
Object.defineProperty(document, 'currentScript', {
value: originalCurrentScript,
writable: true,
});
});
});
@@ -1,66 +0,0 @@
import { logInfo } from '@grafana/runtime';
const cachedMetricProxies = new WeakMap<object, unknown>();
const trackedKeys: Record<string, boolean> = {};
function createMetricsProxy<T extends object>(obj: T, parentName: string, packageName: string): T {
const handler: ProxyHandler<T> = {
get(target, key) {
if (
// plugins are evaluated by SystemJS and not by a browser <script> tag
// if document.currentScript is null this is most likely called by a plugin
document.currentScript === null &&
typeof key !== 'symbol' &&
// __useDefault is a implementation detail of our systemjs plugins
// that we don't want to track
key.toString() !== '__useDefault'
) {
const accessPath = `${parentName}.${String(key)}`;
// we want to report API usage per-plugin when possible
const cacheKey = `${accessPath}`;
if (!trackedKeys[cacheKey]) {
trackedKeys[cacheKey] = true;
// note: intentionally not using shorthand property assignment
// so any future variable name changes won't affect the metrics names
logInfo(`Plugin using ${accessPath}`, {
key: String(key),
parent: parentName,
packageName: packageName,
});
}
}
let value = Reflect.get(target, key);
if (value !== null && typeof value === 'object' && !(value instanceof RegExp)) {
if (!cachedMetricProxies.has(value)) {
cachedMetricProxies.set(value, createMetricsProxy(value, `${parentName}.${String(key)}`, packageName));
}
return cachedMetricProxies.get(value);
}
// proxies don't play nice with functions scopes
if (typeof value === 'function') {
value = value.bind(target);
}
return value;
},
};
if (typeof obj === 'object' && obj !== null) {
return new Proxy(obj, handler);
}
return obj;
}
const trackPackagesRe = /^(@grafana|app\/)/;
export function trackPackageUsage<T extends object>(obj: T, packageName: string): T {
if (trackPackagesRe.test(packageName)) {
return createMetricsProxy(obj, packageName, packageName);
}
return obj;
}