Chore: Clean up code from pluginsAPIMetrics feature toggle (#103965)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user