mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-27 16:30:34 +01:00
Compare commits
1 Commits
feat/authz
...
fix/query-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e52bd3950 |
10
cmd/authz.go
10
cmd/authz.go
@@ -93,13 +93,9 @@ func runGenerateAuthz(_ context.Context) error {
|
||||
registry := coretypes.NewRegistry()
|
||||
|
||||
allowedResources := map[string]bool{
|
||||
coretypes.NewResourceRef(coretypes.ResourceServiceAccount).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceRole).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceFactorAPIKey).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceLogs).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceTraces).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMetrics).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMeterMetrics).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceServiceAccount).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceRole).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceFactorAPIKey).String(): true,
|
||||
}
|
||||
|
||||
allowedTypes := map[string]bool{}
|
||||
|
||||
@@ -76,10 +76,9 @@ function createGrantPermissionAsReadonly(
|
||||
return {
|
||||
label: 'readonly',
|
||||
insertText: resources
|
||||
.filter((r) => {
|
||||
const verbs: readonly string[] = r.allowedVerbs;
|
||||
return verbs.includes('read') || verbs.includes('list');
|
||||
})
|
||||
.filter(
|
||||
(r) => r.allowedVerbs.includes('read') || r.allowedVerbs.includes('list'),
|
||||
)
|
||||
.flatMap((r) => {
|
||||
const verbs = r.allowedVerbs.filter((v) => v === 'read' || v === 'list');
|
||||
return verbs.map(
|
||||
|
||||
@@ -329,15 +329,11 @@ describe('transformTransactionGroupsToResourcePermissions', () => {
|
||||
it('returns all resources from RESOURCE_ORDER even with empty transaction groups', () => {
|
||||
const result = transformTransactionGroupsToResourcePermissions([]);
|
||||
|
||||
expect(result).toHaveLength(7);
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result.map((r) => r.resourceKind)).toStrictEqual([
|
||||
'factor-api-key',
|
||||
'role',
|
||||
'serviceaccount',
|
||||
'logs',
|
||||
'traces',
|
||||
'metrics',
|
||||
'meter-metrics',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -418,15 +414,11 @@ describe('createEmptyRolePermissions', () => {
|
||||
it('creates permissions for all resources in RESOURCE_ORDER', () => {
|
||||
const result = createEmptyRolePermissions();
|
||||
|
||||
expect(result).toHaveLength(7);
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result.map((r) => r.resourceKind)).toStrictEqual([
|
||||
'factor-api-key',
|
||||
'role',
|
||||
'serviceaccount',
|
||||
'logs',
|
||||
'traces',
|
||||
'metrics',
|
||||
'meter-metrics',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
Bot,
|
||||
ChartLine,
|
||||
DraftingCompass,
|
||||
Gauge,
|
||||
Key,
|
||||
Logs,
|
||||
Shield,
|
||||
} from '@signozhq/icons';
|
||||
import { Bot, Key, Shield } from '@signozhq/icons';
|
||||
|
||||
import permissionsType from 'lib/authz/hooks/useAuthZ/permissions.type';
|
||||
import {
|
||||
@@ -58,34 +50,6 @@ export const RESOURCE_PANELS: Record<AuthZResource, ResourcePanelConfig> = {
|
||||
'Type service account ID, separate multiple with comma or space',
|
||||
docsAnchor: 'service-account',
|
||||
},
|
||||
logs: {
|
||||
label: 'Logs',
|
||||
description: 'Log data collected across the workspace.',
|
||||
icon: Logs,
|
||||
selectorPlaceholder: 'Type selector, separate multiple with comma or space',
|
||||
docsAnchor: 'logs',
|
||||
},
|
||||
traces: {
|
||||
label: 'Traces',
|
||||
description: 'Distributed tracing data collected across the workspace.',
|
||||
icon: DraftingCompass,
|
||||
selectorPlaceholder: 'Type selector, separate multiple with comma or space',
|
||||
docsAnchor: 'traces',
|
||||
},
|
||||
metrics: {
|
||||
label: 'Metrics',
|
||||
description: 'Metric data collected across the workspace.',
|
||||
icon: ChartLine,
|
||||
selectorPlaceholder: 'Type selector, separate multiple with comma or space',
|
||||
docsAnchor: 'metrics',
|
||||
},
|
||||
'meter-metrics': {
|
||||
label: 'Meter Metrics',
|
||||
description: 'Usage metering data for the workspace.',
|
||||
icon: Gauge,
|
||||
selectorPlaceholder: 'Type selector, separate multiple with comma or space',
|
||||
docsAnchor: 'meter-metrics',
|
||||
},
|
||||
};
|
||||
|
||||
export const RESOURCE_ORDER = Object.keys(RESOURCE_PANELS) as AuthZResource[];
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('PermissionDeniedCallout', () => {
|
||||
it('renders multiple denied permissions', () => {
|
||||
const deniedPermissions = [
|
||||
buildPermission('read', buildObjectString('serviceaccount', '*')),
|
||||
buildPermission('update', buildObjectString<'update'>('role', 'admin')),
|
||||
buildPermission('update', buildObjectString('role', 'admin')),
|
||||
];
|
||||
render(<PermissionDeniedCallout deniedPermissions={deniedPermissions} />);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('PermissionDeniedFullPage', () => {
|
||||
it('renders with multiple denied permissions', () => {
|
||||
const deniedPermissions = [
|
||||
buildPermission('read', buildObjectString('role', 'admin')),
|
||||
buildPermission('update', buildObjectString<'update'>('role', 'admin')),
|
||||
buildPermission('update', buildObjectString('role', 'admin')),
|
||||
];
|
||||
render(<PermissionDeniedFullPage deniedPermissions={deniedPermissions} />);
|
||||
expect(screen.getByText(/read:role:admin/)).toBeInTheDocument();
|
||||
|
||||
@@ -35,26 +35,6 @@ export default {
|
||||
'update',
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: 'logs',
|
||||
type: 'telemetryresource',
|
||||
allowedVerbs: ['read'],
|
||||
},
|
||||
{
|
||||
kind: 'meter-metrics',
|
||||
type: 'telemetryresource',
|
||||
allowedVerbs: ['read'],
|
||||
},
|
||||
{
|
||||
kind: 'metrics',
|
||||
type: 'telemetryresource',
|
||||
allowedVerbs: ['read'],
|
||||
},
|
||||
{
|
||||
kind: 'traces',
|
||||
type: 'telemetryresource',
|
||||
allowedVerbs: ['read'],
|
||||
},
|
||||
],
|
||||
relations: {
|
||||
assignee: ['role'],
|
||||
@@ -63,7 +43,7 @@ export default {
|
||||
delete: ['metaresource', 'role', 'serviceaccount'],
|
||||
detach: ['metaresource', 'role', 'serviceaccount'],
|
||||
list: ['metaresource', 'role', 'serviceaccount'],
|
||||
read: ['metaresource', 'role', 'serviceaccount', 'telemetryresource'],
|
||||
read: ['metaresource', 'role', 'serviceaccount'],
|
||||
update: ['metaresource', 'role', 'serviceaccount'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
compareTableColumnValues,
|
||||
RowData,
|
||||
} from '../createTableColumnsFromQuery';
|
||||
|
||||
// Builds a minimal RowData row. Values are intentionally loosely typed because
|
||||
// real query responses can put objects/arrays into cells despite RowData's
|
||||
// declared `string | number` index signature (that mismatch is the bug under test).
|
||||
const row = (value: unknown, dataIndex = 'col'): RowData =>
|
||||
({
|
||||
timestamp: 0,
|
||||
key: 'k',
|
||||
[dataIndex]: value,
|
||||
}) as unknown as RowData;
|
||||
|
||||
describe('compareTableColumnValues', () => {
|
||||
it('sorts numerically when both cells are numbers', () => {
|
||||
expect(compareTableColumnValues(row(1), row(2), 'col')).toBeLessThan(0);
|
||||
expect(compareTableColumnValues(row(2), row(1), 'col')).toBeGreaterThan(0);
|
||||
expect(compareTableColumnValues(row(5), row(5), 'col')).toBe(0);
|
||||
});
|
||||
|
||||
it('sorts numeric-looking strings numerically, not lexically', () => {
|
||||
// "10" vs "9": numeric branch => 10 - 9 > 0. A string compare would be < 0.
|
||||
expect(compareTableColumnValues(row('10'), row('9'), 'col')).toBeGreaterThan(
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
it('prefers the `<dataIndex>_without_unit` value for numeric comparison', () => {
|
||||
const a = row('2 ms');
|
||||
const b = row('10 ms');
|
||||
a.col_without_unit = 2;
|
||||
b.col_without_unit = 10;
|
||||
expect(compareTableColumnValues(a, b, 'col')).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it('falls back to locale string compare for non-numeric strings', () => {
|
||||
expect(compareTableColumnValues(row('abc'), row('abd'), 'col')).toBe(
|
||||
'abc'.localeCompare('abd'),
|
||||
);
|
||||
});
|
||||
|
||||
it('treats null/undefined cells as empty string (no "null"/"undefined")', () => {
|
||||
// Empty string sorts before a real word, and two empties are equal.
|
||||
expect(compareTableColumnValues(row(null), row('a'), 'col')).toBeLessThan(0);
|
||||
expect(compareTableColumnValues(row(undefined), row(undefined), 'col')).toBe(
|
||||
0,
|
||||
);
|
||||
// If null coerced to the literal "null", this would sort after "a".
|
||||
expect(
|
||||
compareTableColumnValues(row(null), row('a'), 'col'),
|
||||
).not.toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('does not throw when a cell value is an object', () => {
|
||||
const a = row({ foo: 'bar' });
|
||||
const b = row({ foo: 'baz' });
|
||||
expect(() => compareTableColumnValues(a, b, 'col')).not.toThrow();
|
||||
expect(typeof compareTableColumnValues(a, b, 'col')).toBe('number');
|
||||
});
|
||||
|
||||
it('does not throw when a cell value is an array', () => {
|
||||
const a = row([1, 2]);
|
||||
const b = row([3, 4]);
|
||||
expect(() => compareTableColumnValues(a, b, 'col')).not.toThrow();
|
||||
// String([1,2]) === "1,2" < String([3,4]) === "3,4"
|
||||
expect(compareTableColumnValues(a, b, 'col')).toBeLessThan(0);
|
||||
});
|
||||
|
||||
it('does not throw when one cell is numeric and the other is an object', () => {
|
||||
const a = row(42);
|
||||
const b = row({ foo: 'bar' });
|
||||
expect(() => compareTableColumnValues(a, b, 'col')).not.toThrow();
|
||||
expect(typeof compareTableColumnValues(a, b, 'col')).toBe('number');
|
||||
});
|
||||
|
||||
it('does not throw for a number cell compared against an "N/A" cell', () => {
|
||||
// http.status_code column: [null, "200", ...] -> ["N/A", 200, ...]
|
||||
const numberCell = row(200); // numeric string "200" becomes the number 200
|
||||
const naCell = row('N/A'); // null becomes the string "N/A"
|
||||
|
||||
// Both orderings — antd's sorter compares pairs in both directions.
|
||||
expect(() =>
|
||||
compareTableColumnValues(numberCell, naCell, 'col'),
|
||||
).not.toThrow();
|
||||
expect(() =>
|
||||
compareTableColumnValues(naCell, numberCell, 'col'),
|
||||
).not.toThrow();
|
||||
expect(typeof compareTableColumnValues(numberCell, naCell, 'col')).toBe(
|
||||
'number',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -637,6 +637,21 @@ const generateData = (
|
||||
return data;
|
||||
};
|
||||
|
||||
export const compareTableColumnValues = (
|
||||
a: RowData,
|
||||
b: RowData,
|
||||
dataIndex: string,
|
||||
): number => {
|
||||
const valueA = Number(a[`${dataIndex}_without_unit`] ?? a[dataIndex]);
|
||||
const valueB = Number(b[`${dataIndex}_without_unit`] ?? b[dataIndex]);
|
||||
|
||||
if (!isNaN(valueA) && !isNaN(valueB)) {
|
||||
return valueA - valueB;
|
||||
}
|
||||
|
||||
return String(a[dataIndex] ?? '').localeCompare(String(b[dataIndex] ?? ''));
|
||||
};
|
||||
|
||||
const generateTableColumns = (
|
||||
dynamicColumns: DynamicColumns,
|
||||
renderColumnCell?: QueryTableProps['renderColumnCell'],
|
||||
@@ -650,18 +665,8 @@ const generateTableColumns = (
|
||||
title: item.title,
|
||||
width: QUERY_TABLE_CONFIG.width,
|
||||
render: renderColumnCell && renderColumnCell[dataIndex],
|
||||
sorter: (a: RowData, b: RowData): number => {
|
||||
const valueA = Number(a[`${dataIndex}_without_unit`] ?? a[dataIndex]);
|
||||
const valueB = Number(b[`${dataIndex}_without_unit`] ?? b[dataIndex]);
|
||||
|
||||
if (!isNaN(valueA) && !isNaN(valueB)) {
|
||||
return valueA - valueB;
|
||||
}
|
||||
|
||||
return ((a[dataIndex] as string) || '').localeCompare(
|
||||
(b[dataIndex] as string) || '',
|
||||
);
|
||||
},
|
||||
sorter: (a: RowData, b: RowData): number =>
|
||||
compareTableColumnValues(a, b, dataIndex),
|
||||
};
|
||||
|
||||
return [...acc, column];
|
||||
|
||||
Reference in New Issue
Block a user