Compare commits

..

5 Commits

Author SHA1 Message Date
nityanandagohain
884120ba1f fix: address comments 2026-05-06 15:45:35 +05:30
nityanandagohain
a61cbf45db Merge remote-tracking branch 'origin/main' into issue_4361_1 2026-05-06 14:41:28 +05:30
primus-bot[bot]
6d0e60822c chore(release): bump to v0.122.0 (#11204)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2026-05-06 08:01:25 +00:00
nityanandagohain
c26c875cbf fix: remove nullable from fieldcontext 2026-04-29 17:41:13 +05:30
nityanandagohain
9499228398 feat: module and store for span mapper 2026-04-28 16:08:44 +05:30
36 changed files with 684 additions and 484 deletions

View File

@@ -190,7 +190,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.121.1
image: signoz/signoz:v0.122.0
ports:
- "8080:8080" # signoz port
# - "6060:6060" # pprof port

View File

@@ -117,7 +117,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.121.1
image: signoz/signoz:v0.122.0
ports:
- "8080:8080" # signoz port
volumes:

View File

@@ -181,7 +181,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.121.1}
image: signoz/signoz:${VERSION:-v0.122.0}
container_name: signoz
ports:
- "8080:8080" # signoz port

View File

@@ -109,7 +109,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.121.1}
image: signoz/signoz:${VERSION:-v0.122.0}
container_name: signoz
ports:
- "8080:8080" # signoz port

View File

@@ -4919,8 +4919,6 @@ components:
type: object
SpantypesPostableSpanMapperGroup:
properties:
category:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
enabled:
@@ -4929,7 +4927,6 @@ components:
type: string
required:
- name
- category
- condition
type: object
SpantypesSpanMapper:
@@ -4976,8 +4973,6 @@ components:
type: object
SpantypesSpanMapperGroup:
properties:
category:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
createdAt:
@@ -5002,13 +4997,11 @@ components:
- id
- orgId
- name
- category
- condition
- enabled
type: object
SpantypesSpanMapperGroupCategory:
type: object
SpantypesSpanMapperGroupCondition:
nullable: true
properties:
attributes:
items:
@@ -10099,12 +10092,6 @@ paths:
org.
operationId: ListSpanMapperGroups
parameters:
- explode: true
in: query
name: category
schema:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCategory'
style: deepObject
- in: query
name: enabled
schema:

View File

@@ -165,8 +165,6 @@ function createMockAppContext(
orgPreferences: createMockOrgPreferences(),
userPreferences: [],
isLoggedIn: true,
isNoAuthMode: false,
isPreflightLoading: false,
org: [{ createdAt: 0, id: 'org-id', displayName: 'Test Org' }],
isFetchingUser: false,
isFetchingActiveLicense: false,

View File

@@ -58,7 +58,6 @@ function App(): JSX.Element {
isLoggedIn: isLoggedInState,
featureFlags,
org,
isPreflightLoading,
} = useAppContext();
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
@@ -351,10 +350,6 @@ function App(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isCloudUser, isEnterpriseSelfHostedUser]);
if (isPreflightLoading) {
return <Spinner tip="Loading..." />;
}
// if the user is in logged in state
if (isLoggedInState) {
// if the setup calls are loading then return a spinner

View File

@@ -1,69 +0,0 @@
import axios from 'axios';
import { LOCALSTORAGE } from 'constants/localStorage';
import { interceptorRejected } from '../index';
jest.mock('api/v2/sessions/rotate/post', () => ({
__esModule: true,
default: jest.fn(),
}));
jest.mock('AppRoutes/utils', () => ({
__esModule: true,
default: jest.fn(),
}));
jest.mock('../utils', () => ({
Logout: jest.fn(),
}));
// oxlint-disable-next-line typescript/no-require-imports typescript/no-var-requires
const post = require('api/v2/sessions/rotate/post').default;
// oxlint-disable-next-line typescript/no-require-imports typescript/no-var-requires
const { Logout } = require('../utils');
describe('interceptorRejected — no-auth mode', () => {
beforeEach(() => {
jest.clearAllMocks();
localStorage.clear();
jest.spyOn(axios, 'isAxiosError').mockReturnValue(true);
});
it('does NOT call rotate or Logout when IS_NO_AUTH_MODE=true on 401', async () => {
localStorage.setItem(LOCALSTORAGE.IS_NO_AUTH_MODE, 'true');
const error = {
isAxiosError: true,
response: {
status: 401,
config: { url: '/dashboards', method: 'get' },
},
config: { url: '/dashboards', headers: {} },
};
await interceptorRejected(error as any).catch(() => {});
expect(post).not.toHaveBeenCalled();
expect(Logout).not.toHaveBeenCalled();
});
it('DOES attempt rotate when IS_NO_AUTH_MODE=false on 401', async () => {
localStorage.setItem(LOCALSTORAGE.IS_NO_AUTH_MODE, 'false');
(post as jest.Mock).mockResolvedValue({
data: { accessToken: 'a', refreshToken: 'b' },
});
const error = {
isAxiosError: true,
response: {
status: 401,
config: { url: '/dashboards', method: 'get' },
},
config: { url: '/dashboards', headers: {} },
};
await interceptorRejected(error as any).catch(() => {});
expect(post).toHaveBeenCalled();
});
});

View File

@@ -7200,7 +7200,6 @@ export interface SpantypesPostableSpanMapperDTO {
}
export interface SpantypesPostableSpanMapperGroupDTO {
category: SpantypesSpanMapperGroupCategoryDTO;
condition: SpantypesSpanMapperGroupConditionDTO;
/**
* @type boolean
@@ -7260,7 +7259,6 @@ export interface SpantypesSpanMapperConfigDTO {
}
export interface SpantypesSpanMapperGroupDTO {
category: SpantypesSpanMapperGroupCategoryDTO;
condition: SpantypesSpanMapperGroupConditionDTO;
/**
* @type string
@@ -7298,11 +7296,10 @@ export interface SpantypesSpanMapperGroupDTO {
updatedBy?: string;
}
export interface SpantypesSpanMapperGroupCategoryDTO {
[key: string]: unknown;
}
export interface SpantypesSpanMapperGroupConditionDTO {
/**
* @nullable
*/
export type SpantypesSpanMapperGroupConditionDTO = {
/**
* @type array
* @nullable true
@@ -7313,7 +7310,7 @@ export interface SpantypesSpanMapperGroupConditionDTO {
* @nullable true
*/
resource: string[] | null;
}
} | null;
export enum SpantypesSpanMapperOperationDTO {
move = 'move',
@@ -8887,10 +8884,6 @@ export type GetMyServiceAccount200 = {
};
export type ListSpanMapperGroupsParams = {
/**
* @description undefined
*/
category?: SpantypesSpanMapperGroupCategoryDTO;
/**
* @type boolean
* @nullable true

View File

@@ -108,11 +108,7 @@ export const interceptorRejected = async (
if (axios.isAxiosError(value) && value.response) {
const { response } = value;
const isNoAuthMode =
getLocalStorageApi(LOCALSTORAGE.IS_NO_AUTH_MODE) === 'true';
if (
!isNoAuthMode &&
response.status === 401 &&
// if the session rotate call or the create session errors out with 401 or the delete sessions call returns 401 then we do not retry!
response.config.url !== '/sessions/rotate' &&
@@ -144,20 +140,16 @@ export const interceptorRejected = async (
return await Promise.resolve(reResponse);
} catch (error) {
if ((error as AxiosError)?.response?.status === 401) {
void Logout();
Logout();
}
}
} catch (error) {
void Logout();
Logout();
}
}
if (
!isNoAuthMode &&
response.status === 401 &&
response.config.url === '/sessions/rotate'
) {
void Logout();
if (response.status === 401 && response.config.url === '/sessions/rotate') {
Logout();
}
}
return await Promise.reject(value);

View File

@@ -39,5 +39,4 @@ export enum LOCALSTORAGE {
DISMISSED_API_KEYS_DEPRECATION_BANNER = 'DISMISSED_API_KEYS_DEPRECATION_BANNER',
LICENSE_KEY_CALLOUT_DISMISSED = 'LICENSE_KEY_CALLOUT_DISMISSED',
DASHBOARD_PREFERENCES = 'DASHBOARD_PREFERENCES',
IS_NO_AUTH_MODE = 'IS_NO_AUTH_MODE',
}

View File

@@ -9,7 +9,6 @@ import EditMemberDrawer from 'components/EditMemberDrawer/EditMemberDrawer';
import InviteMembersModal from 'components/InviteMembersModal/InviteMembersModal';
import MembersTable, { MemberRow } from 'components/MembersTable/MembersTable';
import useUrlQuery from 'hooks/useUrlQuery';
import { useAppContext } from 'providers/App/App';
import { toISOString } from 'utils/app';
import { FilterMode, MemberStatus, toMemberStatus } from './utils';
@@ -21,7 +20,6 @@ const PAGE_SIZE = 20;
function MembersSettings(): JSX.Element {
const history = useHistory();
const urlQuery = useUrlQuery();
const { isNoAuthMode } = useAppContext();
const pageParam = parseInt(urlQuery.get('page') ?? '1', 10);
const currentPage = Number.isNaN(pageParam) || pageParam < 1 ? 1 : pageParam;
@@ -147,7 +145,7 @@ function MembersSettings(): JSX.Element {
: `Deleted ⎯ ${deletedCount}`;
const handleInviteComplete = useCallback((): void => {
void refetchUsers();
refetchUsers();
}, [refetchUsers]);
const handleRowClick = useCallback((member: MemberRow): void => {
@@ -159,7 +157,7 @@ function MembersSettings(): JSX.Element {
}, []);
const handleMemberEditComplete = useCallback((): void => {
void refetchUsers();
refetchUsers();
}, [refetchUsers]);
return (
@@ -202,16 +200,14 @@ function MembersSettings(): JSX.Element {
/>
</div>
{!isNoAuthMode && (
<Button
variant="solid"
color="primary"
onClick={(): void => setIsInviteModalOpen(true)}
>
<Plus size={12} />
Invite member
</Button>
)}
<Button
variant="solid"
color="primary"
onClick={(): void => setIsInviteModalOpen(true)}
>
<Plus size={12} />
Invite member
</Button>
</div>
</div>
<MembersTable
@@ -225,13 +221,11 @@ function MembersSettings(): JSX.Element {
onRowClick={handleRowClick}
/>
{!isNoAuthMode && (
<InviteMembersModal
open={isInviteModalOpen}
onClose={(): void => setIsInviteModalOpen(false)}
onComplete={handleInviteComplete}
/>
)}
<InviteMembersModal
open={isInviteModalOpen}
onClose={(): void => setIsInviteModalOpen(false)}
onComplete={handleInviteComplete}
/>
<EditMemberDrawer
member={selectedMember}

View File

@@ -15,7 +15,7 @@ import '../MySettings.styles.scss';
import './UserInfo.styles.scss';
function UserInfo(): JSX.Element {
const { user, org, updateUser, isNoAuthMode } = useAppContext();
const { user, org, updateUser } = useAppContext();
const { t } = useTranslation(['routes', 'settings', 'common']);
const { notifications } = useNotifications();
@@ -79,10 +79,10 @@ function UserInfo(): JSX.Element {
currentPassword === updatePassword;
const onSaveHandler = async (): Promise<void> => {
void logEvent('Account Settings: Name Updated', {
logEvent('Account Settings: Name Updated', {
name: changedName,
});
void logEvent(
logEvent(
'Account Settings: Name Updated',
{
name: changedName,
@@ -143,16 +143,14 @@ function UserInfo(): JSX.Element {
Update name
</Button>
{!isNoAuthMode && (
<Button
type="default"
className="periscope-btn secondary"
icon={<FileTerminal size={16} />}
onClick={(): void => setIsResetPasswordModalOpen(true)}
>
Reset password
</Button>
)}
<Button
type="default"
className="periscope-btn secondary"
icon={<FileTerminal size={16} />}
onClick={(): void => setIsResetPasswordModalOpen(true)}
>
Reset password
</Button>
</div>
<Modal
@@ -184,64 +182,62 @@ function UserInfo(): JSX.Element {
</div>
</Modal>
{!isNoAuthMode && (
<Modal
className="reset-password-modal"
title={<span className="title">Reset password</span>}
open={isResetPasswordModalOpen}
closable
onCancel={hideResetPasswordModal}
footer={[
<Button
key="submit"
className={`periscope-btn ${
isResetPasswordDisabled ? 'secondary' : 'primary'
}`}
icon={<Check size={16} />}
onClick={onChangePasswordClickHandler}
disabled={isLoading || isResetPasswordDisabled}
data-testid="reset-password-btn"
>
Reset password
</Button>,
]}
>
<div className="reset-password-container">
<div className="current-password-input">
<Typography.Text>Current password</Typography.Text>
<Input.Password
data-testid="current-password-textbox"
disabled={isLoading}
placeholder={defaultPlaceHolder}
onChange={(event): void => {
setCurrentPassword(event.target.value);
}}
value={currentPassword}
type="password"
autoComplete="off"
visibilityToggle
/>
</div>
<div className="new-password-input">
<Typography.Text>New password</Typography.Text>
<Input.Password
data-testid="new-password-textbox"
disabled={isLoading}
placeholder={defaultPlaceHolder}
onChange={(event): void => {
const updatedValue = event.target.value;
setUpdatePassword(updatedValue);
}}
value={updatePassword}
type="password"
autoComplete="off"
visibilityToggle={false}
/>
</div>
<Modal
className="reset-password-modal"
title={<span className="title">Reset password</span>}
open={isResetPasswordModalOpen}
closable
onCancel={hideResetPasswordModal}
footer={[
<Button
key="submit"
className={`periscope-btn ${
isResetPasswordDisabled ? 'secondary' : 'primary'
}`}
icon={<Check size={16} />}
onClick={onChangePasswordClickHandler}
disabled={isLoading || isResetPasswordDisabled}
data-testid="reset-password-btn"
>
Reset password
</Button>,
]}
>
<div className="reset-password-container">
<div className="current-password-input">
<Typography.Text>Current password</Typography.Text>
<Input.Password
data-testid="current-password-textbox"
disabled={isLoading}
placeholder={defaultPlaceHolder}
onChange={(event): void => {
setCurrentPassword(event.target.value);
}}
value={currentPassword}
type="password"
autoComplete="off"
visibilityToggle
/>
</div>
</Modal>
)}
<div className="new-password-input">
<Typography.Text>New password</Typography.Text>
<Input.Password
data-testid="new-password-textbox"
disabled={isLoading}
placeholder={defaultPlaceHolder}
onChange={(event): void => {
const updatedValue = event.target.value;
setUpdatePassword(updatedValue);
}}
value={updatePassword}
type="password"
autoComplete="off"
visibilityToggle={false}
/>
</div>
</div>
</Modal>
</div>
);
}

View File

@@ -100,8 +100,6 @@ export function getAppContextMockState(
orgPreferences: null,
userPreferences: null,
isLoggedIn: false,
isNoAuthMode: false,
isPreflightLoading: false,
org: null,
isFetchingUser: false,
isFetchingActiveLicense: false,

View File

@@ -131,7 +131,6 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
featureFlags,
trialInfo,
isLoggedIn,
isNoAuthMode,
userPreferences,
changelog,
toggleChangelogModal,
@@ -402,7 +401,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
);
const handleReorderShortcutNavItems = (): void => {
void logEvent('Sidebar V2: Save shortcuts clicked', {
logEvent('Sidebar V2: Save shortcuts clicked', {
shortcuts: tempPinnedMenuItems.map((item) => item.key),
});
setPinnedMenuItems(tempPinnedMenuItems);
@@ -430,7 +429,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
const isWorkspaceBlocked = trialInfo?.workSpaceBlock || false;
const onClickGetStarted = (event: MouseEvent): void => {
void logEvent('Sidebar: Menu clicked', {
logEvent('Sidebar: Menu clicked', {
menuRoute: '/get-started',
menuLabel: 'Get Started',
});
@@ -483,14 +482,12 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
isWorkspaceBlocked,
isEnterpriseSelfHostedUser,
isCommunityEnterpriseUser,
isNoAuthMode,
}),
[
isEnterpriseSelfHostedUser,
isCommunityEnterpriseUser,
user.email,
isWorkspaceBlocked,
isNoAuthMode,
],
);
@@ -512,7 +509,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
]);
useEffect(() => {
if (!isAdmin || isNoAuthMode) {
if (!isAdmin) {
setHelpSupportDropdownMenuItems((prevState) =>
prevState.filter(
(item) => !('key' in item) || item.key !== 'invite-collaborators',
@@ -603,7 +600,6 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
isAdmin,
isNoAuthMode,
isChatSupportEnabled,
isPremiumSupportEnabled,
isCloudUser,
@@ -632,7 +628,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
} else if (item) {
onClickHandler(item?.key as string, event);
}
void logEvent('Sidebar V2: Menu clicked', {
logEvent('Sidebar V2: Menu clicked', {
menuRoute: item?.key,
menuLabel: item?.label,
});
@@ -775,7 +771,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
onTogglePin={
allowPin
? (item): void => {
void logEvent(
logEvent(
`Sidebar V2: Menu item ${item.isPinned ? 'unpinned' : 'pinned'}`,
{
menuRoute: item.key,
@@ -822,7 +818,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
const event = (info as SidebarItem & { domEvent?: MouseEvent }).domEvent;
if (item && !('type' in item)) {
void logEvent('Help Popover: Item clicked', {
logEvent('Help Popover: Item clicked', {
menuRoute: item.key,
menuLabel: String(item.label),
});
@@ -871,7 +867,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
menuLabel = item.label;
}
void logEvent('Settings Popover: Item clicked', {
logEvent('Settings Popover: Item clicked', {
menuRoute: item?.key,
menuLabel,
});
@@ -908,7 +904,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
}
break;
case 'logout':
void Logout();
Logout();
break;
default:
}
@@ -1062,7 +1058,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
<div
className="nav-section-title-icon reorder"
onClick={(): void => {
void logEvent('Sidebar V2: Manage shortcuts clicked', {});
logEvent('Sidebar V2: Manage shortcuts clicked', {});
setIsReorderShortcutNavItemsModalOpen(true);
}}
>
@@ -1109,7 +1105,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
return;
}
const newCollapsedState = !isMoreMenuCollapsed;
void logEvent('Sidebar V2: More menu clicked', {
logEvent('Sidebar V2: More menu clicked', {
action: isMoreMenuCollapsed ? 'expand' : 'collapse',
});
setIsMoreMenuCollapsed(newCollapsedState);
@@ -1213,14 +1209,14 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
open={isReorderShortcutNavItemsModalOpen}
closable
onCancel={(): void => {
void logEvent('Sidebar V2: Manage shortcuts dismissed', {});
logEvent('Sidebar V2: Manage shortcuts dismissed', {});
hideReorderShortcutNavItemsModal();
}}
footer={[
<Button
key="cancel"
onClick={(): void => {
void logEvent('Sidebar V2: Manage shortcuts dismissed', {});
logEvent('Sidebar V2: Manage shortcuts dismissed', {});
hideReorderShortcutNavItemsModal();
}}
className="periscope-btn cancel-btn secondary-btn"

View File

@@ -5,7 +5,6 @@ const BASE_PARAMS = {
isWorkspaceBlocked: false,
isEnterpriseSelfHostedUser: false,
isCommunityEnterpriseUser: false,
isNoAuthMode: false,
};
describe('getUserSettingsDropdownMenuItems', () => {
@@ -72,15 +71,4 @@ describe('getUserSettingsDropdownMenuItems', () => {
expect(keys[3]).toBe('account');
expect(keys[keys.length - 1]).toBe('logout');
});
it('omits sign out and its preceding divider when isNoAuthMode=true', () => {
const items =
getUserSettingsDropdownMenuItems({ ...BASE_PARAMS, isNoAuthMode: true }) ??
[];
const keys = items.map((item: any) => item.key ?? item.type);
expect(keys).not.toContain('logout');
// the trailing divider before logout should also be gone
expect(keys[keys.length - 1]).toBe('keyboard-shortcuts');
});
});

View File

@@ -470,7 +470,6 @@ export interface UserSettingsMenuItemsParams {
isWorkspaceBlocked: boolean;
isEnterpriseSelfHostedUser: boolean;
isCommunityEnterpriseUser: boolean;
isNoAuthMode: boolean;
}
export const getUserSettingsDropdownMenuItems = ({
@@ -478,7 +477,6 @@ export const getUserSettingsDropdownMenuItems = ({
isWorkspaceBlocked,
isEnterpriseSelfHostedUser,
isCommunityEnterpriseUser,
isNoAuthMode,
}: UserSettingsMenuItemsParams): MenuProps['items'] =>
[
{
@@ -522,25 +520,21 @@ export const getUserSettingsDropdownMenuItems = ({
icon: <Keyboard size={14} color={Style.L1_FOREGROUND} />,
dataTestId: 'keyboard-shortcuts-nav-item',
},
...(isNoAuthMode
? []
: [
{ type: 'divider' as const },
{
key: 'logout',
label: (
<span className="user-settings-dropdown-logout-section">Sign out</span>
),
icon: (
<LogOut
size={14}
className="user-settings-dropdown-logout-section"
color={Style.DANGER_BACKGROUND}
/>
),
dataTestId: 'logout-nav-item',
},
]),
{ type: 'divider' as const },
{
key: 'logout',
label: (
<span className="user-settings-dropdown-logout-section">Sign out</span>
),
icon: (
<LogOut
size={14}
className="user-settings-dropdown-logout-section"
color={Style.DANGER_BACKGROUND}
/>
),
dataTestId: 'logout-nav-item',
},
].filter(Boolean);
/** Mapping of some newly added routes and their corresponding active sidebar menu key */

View File

@@ -26,13 +26,8 @@ import './Settings.styles.scss';
function SettingsPage(): JSX.Element {
const { pathname, search } = useLocation();
const {
user,
featureFlags,
trialInfo,
isFetchingActiveLicense,
isNoAuthMode,
} = useAppContext();
const { user, featureFlags, trialInfo, isFetchingActiveLicense } =
useAppContext();
const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense();
const [settingsMenuItems, setSettingsMenuItems] = useState<SidebarItem[]>(
@@ -181,14 +176,6 @@ function SettingsPage(): JSX.Element {
}));
}
// In no-auth mode, hide the Members page from the sidebar
if (isNoAuthMode) {
updatedItems = updatedItems.map((item) => ({
...item,
isEnabled: item.key === ROUTES.MEMBERS_SETTINGS ? false : item.isEnabled,
}));
}
return updatedItems;
});
}, [
@@ -198,7 +185,6 @@ function SettingsPage(): JSX.Element {
isCloudUser,
isEnterpriseSelfHostedUser,
isFetchingActiveLicense,
isNoAuthMode,
trialInfo?.workSpaceBlock,
pathname,
]);
@@ -213,7 +199,6 @@ function SettingsPage(): JSX.Element {
isCloudUser,
isEnterpriseSelfHostedUser,
t,
isNoAuthMode,
),
[
user.role,
@@ -223,7 +208,6 @@ function SettingsPage(): JSX.Element {
isCloudUser,
isEnterpriseSelfHostedUser,
t,
isNoAuthMode,
],
);
@@ -314,7 +298,7 @@ function SettingsPage(): JSX.Element {
isDisabled={false}
showIcon={false}
onClick={(event): void => {
void logEvent('Settings V2: Menu clicked', {
logEvent('Settings V2: Menu clicked', {
menuLabel: item.label,
menuRoute: item.key,
});

View File

@@ -28,7 +28,6 @@ export const getRoutes = (
isCloudUser: boolean,
isEnterpriseSelfHostedUser: boolean,
t: TFunction,
isNoAuthMode = false,
): RouteTabProps['routes'] => {
const settings = [];
@@ -38,7 +37,7 @@ export const getRoutes = (
if (isWorkspaceBlocked && isAdmin) {
settings.push(
...organizationSettings(t),
...(isNoAuthMode ? [] : membersSettings(t)),
...membersSettings(t),
...mySettings(t),
...billingSettings(t),
...keyboardShortcuts(t),
@@ -65,7 +64,7 @@ export const getRoutes = (
if (isAdmin) {
settings.push(
...(isNoAuthMode ? [] : membersSettings(t)),
...membersSettings(t),
...serviceAccountsSettings(t),
...rolesSettings(t),
...roleDetails(t),

View File

@@ -12,10 +12,8 @@ import {
import { useQuery } from 'react-query';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import { useGetGlobalConfig } from 'api/generated/services/global';
import { useGetMyUser } from 'api/generated/services/users';
import listOrgPreferences from 'api/v1/org/preferences/list';
import { clearAuthStorage } from 'utils/clearAuthStorage';
import listUserPreferences from 'api/v1/user/preferences/list';
import getUserVersion from 'api/v1/version/get';
import { LOCALSTORAGE } from 'constants/localStorage';
@@ -70,50 +68,11 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(
(): boolean => getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true',
);
const [isNoAuthMode, setIsNoAuthMode] = useState<boolean>(false);
const [isPreflightLoading, setIsPreflightLoading] = useState<boolean>(true);
const [org, setOrg] = useState<Organization[] | null>(null);
const [changelog, setChangelog] = useState<ChangelogSchema | null>(null);
const [showChangelogModal, setShowChangelogModal] = useState<boolean>(false);
// Pre-flight: discover auth mode from public global config.
// On success: in impersonation mode → clear stale tokens, force isLoggedIn=true,
// persist IS_NO_AUTH_MODE flag so the axios interceptor (outside React)
// can skip the rotate-logout chain.
// On failure: fail-safe to normal auth flow (treat as not no-auth).
const { data: globalConfigData, isLoading: isFetchingGlobalConfig } =
useGetGlobalConfig({
query: {
retry: 2,
retryDelay: 1000,
refetchOnWindowFocus: false,
staleTime: Infinity,
},
});
useEffect(() => {
if (isFetchingGlobalConfig) {
return;
}
const impersonationEnabled =
globalConfigData?.data?.identN?.impersonation?.enabled === true;
if (impersonationEnabled) {
clearAuthStorage();
setLocalStorageApi(LOCALSTORAGE.IS_NO_AUTH_MODE, 'true');
setLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN, 'true');
setIsNoAuthMode(true);
setIsLoggedIn(true);
} else {
setLocalStorageApi(LOCALSTORAGE.IS_NO_AUTH_MODE, 'false');
setIsNoAuthMode(false);
}
setIsPreflightLoading(false);
}, [globalConfigData, isFetchingGlobalConfig]);
// fetcher for current user
// user will only be fetched if the user id and token is present
// if logged out and trying to hit any route none of these calls will trigger
@@ -383,9 +342,6 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
// global event listener for LOGOUT event to clean the app context state
useGlobalEventListener('LOGOUT', () => {
if (isNoAuthMode) {
return;
} // logout is meaningless in no-auth; defensively no-op
setIsLoggedIn(false);
setDefaultUser(getUserDefaults());
setActiveLicense(null);
@@ -404,8 +360,6 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
trialInfo,
orgPreferences,
isLoggedIn,
isNoAuthMode,
isPreflightLoading,
org,
isFetchingUser,
isFetchingActiveLicense,
@@ -441,8 +395,6 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
isFetchingOrgPreferences,
isFetchingUser,
isLoggedIn,
isNoAuthMode,
isPreflightLoading,
org,
orgPreferences,
activeLicenseRefetch,

View File

@@ -18,8 +18,6 @@ export interface IAppContext {
orgPreferences: OrgPreference[] | null;
userPreferences: UserPreference[] | null;
isLoggedIn: boolean;
isNoAuthMode: boolean;
isPreflightLoading: boolean;
org: Organization[] | null;
isFetchingUser: boolean;
isFetchingActiveLicense: boolean;

View File

@@ -237,8 +237,6 @@ export function getAppContextMock(
isFetchingOrgPreferences: false,
orgPreferencesFetchError: null,
isLoggedIn: true,
isNoAuthMode: false,
isPreflightLoading: false,
showChangelogModal: false,
updateUser: jest.fn(),
updateOrg: jest.fn(),

View File

@@ -1,39 +0,0 @@
import { LOCALSTORAGE } from 'constants/localStorage';
import { clearAuthStorage } from '../clearAuthStorage';
describe('clearAuthStorage', () => {
beforeEach(() => {
localStorage.clear();
});
it('removes all auth-related localStorage keys', () => {
localStorage.setItem(LOCALSTORAGE.AUTH_TOKEN, 'access');
localStorage.setItem(LOCALSTORAGE.REFRESH_AUTH_TOKEN, 'refresh');
localStorage.setItem(LOCALSTORAGE.IS_LOGGED_IN, 'true');
localStorage.setItem(LOCALSTORAGE.LOGGED_IN_USER_EMAIL, 'old@example.com');
localStorage.setItem(LOCALSTORAGE.LOGGED_IN_USER_NAME, 'old');
localStorage.setItem(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true');
localStorage.setItem(LOCALSTORAGE.USER_ID, 'abc');
clearAuthStorage();
expect(localStorage.getItem(LOCALSTORAGE.AUTH_TOKEN)).toBeNull();
expect(localStorage.getItem(LOCALSTORAGE.REFRESH_AUTH_TOKEN)).toBeNull();
expect(localStorage.getItem(LOCALSTORAGE.IS_LOGGED_IN)).toBeNull();
expect(localStorage.getItem(LOCALSTORAGE.LOGGED_IN_USER_EMAIL)).toBeNull();
expect(localStorage.getItem(LOCALSTORAGE.LOGGED_IN_USER_NAME)).toBeNull();
expect(localStorage.getItem(LOCALSTORAGE.IS_IDENTIFIED_USER)).toBeNull();
expect(localStorage.getItem(LOCALSTORAGE.USER_ID)).toBeNull();
});
it('preserves non-auth localStorage keys', () => {
localStorage.setItem(LOCALSTORAGE.THEME, 'dark');
localStorage.setItem(LOCALSTORAGE.AUTH_TOKEN, 'access');
clearAuthStorage();
expect(localStorage.getItem(LOCALSTORAGE.THEME)).toBe('dark');
expect(localStorage.getItem(LOCALSTORAGE.AUTH_TOKEN)).toBeNull();
});
});

View File

@@ -1,16 +0,0 @@
import deleteLocalStorageKey from 'api/browser/localstorage/remove';
import { LOCALSTORAGE } from 'constants/localStorage';
const AUTH_KEYS: LOCALSTORAGE[] = [
LOCALSTORAGE.AUTH_TOKEN,
LOCALSTORAGE.REFRESH_AUTH_TOKEN,
LOCALSTORAGE.IS_LOGGED_IN,
LOCALSTORAGE.LOGGED_IN_USER_EMAIL,
LOCALSTORAGE.LOGGED_IN_USER_NAME,
LOCALSTORAGE.IS_IDENTIFIED_USER,
LOCALSTORAGE.USER_ID,
];
export const clearAuthStorage = (): void => {
AUTH_KEYS.forEach((key) => deleteLocalStorageKey(key));
};

View File

@@ -6,7 +6,6 @@ import (
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/http/binding"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
@@ -17,12 +16,11 @@ import (
)
type handler struct {
module spanmapper.Module
providerSettings factory.ProviderSettings
module spanmapper.Module
}
func NewHandler(module spanmapper.Module, providerSettings factory.ProviderSettings) spanmapper.Handler {
return &handler{module: module, providerSettings: providerSettings}
func NewHandler(module spanmapper.Module) spanmapper.Handler {
return &handler{module: module}
}
// ListGroups handles GET /api/v1/span_mapper_groups.
@@ -72,10 +70,9 @@ func (h *handler) CreateGroup(rw http.ResponseWriter, r *http.Request) {
return
}
group := spantypes.NewSpanMapperGroupFromPostable(req)
group := spantypes.NewSpanMapperGroup(orgID, claims.Email, req)
err = h.module.CreateGroup(ctx, orgID, claims.Email, group)
if err != nil {
if err := h.module.CreateGroup(ctx, orgID, group); err != nil {
render.Error(rw, err)
return
}
@@ -107,11 +104,17 @@ func (h *handler) UpdateGroup(rw http.ResponseWriter, r *http.Request) {
return
}
err = h.module.UpdateGroup(ctx, orgID, id, claims.Email, spantypes.NewSpanMapperGroupFromUpdatable(req))
group, err := h.module.GetGroup(ctx, orgID, id)
if err != nil {
render.Error(rw, err)
return
}
group.Update(req, claims.Email)
if err := h.module.UpdateGroup(ctx, orgID, id, group); err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
@@ -195,10 +198,9 @@ func (h *handler) CreateMapper(rw http.ResponseWriter, r *http.Request) {
render.Error(rw, err)
return
}
mapper := spantypes.NewSpanMapperFromPostable(req)
mapper := spantypes.NewSpanMapper(groupID, claims.Email, req)
err = h.module.CreateMapper(ctx, orgID, groupID, claims.Email, mapper)
if err != nil {
if err := h.module.CreateMapper(ctx, orgID, groupID, mapper); err != nil {
render.Error(rw, err)
return
}
@@ -237,11 +239,17 @@ func (h *handler) UpdateMapper(rw http.ResponseWriter, r *http.Request) {
return
}
err = h.module.UpdateMapper(ctx, orgID, groupID, mapperID, claims.Email, spantypes.NewSpanMapperFromUpdatable(req))
mapper, err := h.module.GetMapper(ctx, orgID, groupID, mapperID)
if err != nil {
render.Error(rw, err)
return
}
mapper.Update(req, claims.Email)
if err := h.module.UpdateMapper(ctx, orgID, groupID, mapperID, mapper); err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}

View File

@@ -0,0 +1,64 @@
package implspanmapper
import (
"context"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type module struct {
store spantypes.SpanMapperStore
}
func NewModule(store spantypes.SpanMapperStore) spanmapper.Module {
return &module{store: store}
}
func (module *module) ListGroups(ctx context.Context, orgID valuer.UUID, q *spantypes.ListSpanMapperGroupsQuery) ([]*spantypes.SpanMapperGroup, error) {
return module.store.ListGroups(ctx, orgID, q)
}
func (module *module) GetGroup(ctx context.Context, orgID, id valuer.UUID) (*spantypes.SpanMapperGroup, error) {
return module.store.GetGroup(ctx, orgID, id)
}
func (module *module) CreateGroup(ctx context.Context, orgID valuer.UUID, group *spantypes.SpanMapperGroup) error {
return module.store.CreateGroup(ctx, group)
}
func (module *module) UpdateGroup(ctx context.Context, orgID, id valuer.UUID, group *spantypes.SpanMapperGroup) error {
return module.store.UpdateGroup(ctx, group)
}
func (module *module) DeleteGroup(ctx context.Context, orgID, id valuer.UUID) error {
return module.store.DeleteGroup(ctx, orgID, id)
}
func (module *module) ListMappers(ctx context.Context, orgID, groupID valuer.UUID) ([]*spantypes.SpanMapper, error) {
return module.store.ListMappers(ctx, orgID, groupID)
}
func (module *module) GetMapper(ctx context.Context, orgID, groupID, id valuer.UUID) (*spantypes.SpanMapper, error) {
return module.store.GetMapper(ctx, orgID, groupID, id)
}
func (module *module) CreateMapper(ctx context.Context, orgID, groupID valuer.UUID, mapper *spantypes.SpanMapper) error {
// Ensure the group belongs to the org before inserting the child row.
if _, err := module.store.GetGroup(ctx, orgID, groupID); err != nil {
return err
}
return module.store.CreateMapper(ctx, mapper)
}
func (module *module) UpdateMapper(ctx context.Context, orgID, groupID, id valuer.UUID, mapper *spantypes.SpanMapper) error {
if _, err := module.store.GetGroup(ctx, orgID, groupID); err != nil {
return err
}
return module.store.UpdateMapper(ctx, mapper)
}
func (module *module) DeleteMapper(ctx context.Context, orgID, groupID, id valuer.UUID) error {
return module.store.DeleteMapper(ctx, orgID, groupID, id)
}

View File

@@ -0,0 +1,227 @@
package implspanmapper
import (
"context"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type store struct {
sqlstore sqlstore.SQLStore
}
func NewStore(sqlstore sqlstore.SQLStore) spantypes.SpanMapperStore {
return &store{sqlstore: sqlstore}
}
func (s *store) CreateGroup(ctx context.Context, group *spantypes.SpanMapperGroup) error {
storable := group.ToStorable()
_, err := s.sqlstore.
BunDBCtx(ctx).
NewInsert().
Model(storable).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapAlreadyExistsErrf(err, spantypes.ErrCodeMappingGroupAlreadyExists, "span mapper group %q already exists", group.Name)
}
return nil
}
func (s *store) GetGroup(ctx context.Context, orgID, id valuer.UUID) (*spantypes.SpanMapperGroup, error) {
storable := new(spantypes.StorableSpanMapperGroup)
err := s.sqlstore.
BunDB().
NewSelect().
Model(storable).
Where("org_id = ?", orgID).
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, spantypes.ErrCodeMappingGroupNotFound, "span mapper group %s not found", id)
}
return storable.ToSpanMapperGroup(), nil
}
func (s *store) ListGroups(ctx context.Context, orgID valuer.UUID, q *spantypes.ListSpanMapperGroupsQuery) ([]*spantypes.SpanMapperGroup, error) {
storables := make([]*spantypes.StorableSpanMapperGroup, 0)
sel := s.sqlstore.
BunDB().
NewSelect().
Model(&storables).
Where("org_id = ?", orgID)
if q != nil {
if q.Enabled != nil {
sel = sel.Where("enabled = ?", *q.Enabled)
}
}
if err := sel.Order("created_at DESC").Scan(ctx); err != nil {
return nil, err
}
return spantypes.NewSpanMapperGroupsFromStorable(storables), nil
}
func (s *store) UpdateGroup(ctx context.Context, group *spantypes.SpanMapperGroup) error {
storable := group.ToStorable()
res, err := s.sqlstore.
BunDBCtx(ctx).
NewUpdate().
Model(storable).
Where("org_id = ?", storable.OrgID).
Where("id = ?", storable.ID).
Exec(ctx)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.Newf(errors.TypeNotFound, spantypes.ErrCodeMappingGroupNotFound, "span mapper group %s not found", group.ID)
}
return nil
}
func (s *store) DeleteGroup(ctx context.Context, orgID, id valuer.UUID) error {
tx, err := s.sqlstore.BunDBCtx(ctx).BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
// Cascade: remove mappers belonging to this group first.
if _, err := tx.NewDelete().
Model((*spantypes.StorableSpanMapper)(nil)).
Where("group_id = ?", id).
Exec(ctx); err != nil {
return err
}
res, err := tx.NewDelete().
Model((*spantypes.StorableSpanMapperGroup)(nil)).
Where("org_id = ?", orgID).
Where("id = ?", id).
Exec(ctx)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.Newf(errors.TypeNotFound, spantypes.ErrCodeMappingGroupNotFound, "span mapper group %s not found", id)
}
return tx.Commit()
}
func (s *store) CreateMapper(ctx context.Context, mapper *spantypes.SpanMapper) error {
storable := mapper.ToStorable()
_, err := s.sqlstore.
BunDBCtx(ctx).
NewInsert().
Model(storable).
Exec(ctx)
if err != nil {
return s.sqlstore.WrapAlreadyExistsErrf(err, spantypes.ErrCodeMapperAlreadyExists, "span mapper %q already exists", mapper.Name)
}
return nil
}
func (s *store) GetMapper(ctx context.Context, orgID, groupID, id valuer.UUID) (*spantypes.SpanMapper, error) {
// Ensure the group belongs to the org.
if _, err := s.GetGroup(ctx, orgID, groupID); err != nil {
return nil, err
}
storable := new(spantypes.StorableSpanMapper)
err := s.sqlstore.
BunDB().
NewSelect().
Model(storable).
Where("group_id = ?", groupID).
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, s.sqlstore.WrapNotFoundErrf(err, spantypes.ErrCodeMapperNotFound, "span mapper %s not found", id)
}
return storable.ToSpanMapper(), nil
}
func (s *store) ListMappers(ctx context.Context, orgID, groupID valuer.UUID) ([]*spantypes.SpanMapper, error) {
// Scope by org via the parent group's org_id.
if _, err := s.GetGroup(ctx, orgID, groupID); err != nil {
return nil, err
}
storables := make([]*spantypes.StorableSpanMapper, 0)
if err := s.sqlstore.
BunDB().
NewSelect().
Model(&storables).
Where("group_id = ?", groupID).
Order("created_at DESC").
Scan(ctx); err != nil {
return nil, err
}
return spantypes.NewSpanMappersFromStorable(storables), nil
}
func (s *store) UpdateMapper(ctx context.Context, mapper *spantypes.SpanMapper) error {
storable := mapper.ToStorable()
res, err := s.sqlstore.
BunDBCtx(ctx).
NewUpdate().
Model(storable).
Where("group_id = ?", storable.GroupID).
Where("id = ?", storable.ID).
Exec(ctx)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.Newf(errors.TypeNotFound, spantypes.ErrCodeMapperNotFound, "span mapper %s not found", mapper.ID)
}
return nil
}
func (s *store) DeleteMapper(ctx context.Context, orgID, groupID, id valuer.UUID) error {
if _, err := s.GetGroup(ctx, orgID, groupID); err != nil {
return err
}
res, err := s.sqlstore.
BunDBCtx(ctx).
NewDelete().
Model((*spantypes.StorableSpanMapper)(nil)).
Where("group_id = ?", groupID).
Where("id = ?", id).
Exec(ctx)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return errors.Newf(errors.TypeNotFound, spantypes.ErrCodeMapperNotFound, "span mapper %s not found", id)
}
return nil
}

View File

@@ -12,17 +12,17 @@ import (
type Module interface {
// Group operations
ListGroups(ctx context.Context, orgID valuer.UUID, q *spantypes.ListSpanMapperGroupsQuery) ([]*spantypes.SpanMapperGroup, error)
GetGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*spantypes.SpanMapperGroup, error)
CreateGroup(ctx context.Context, orgID valuer.UUID, createdBy string, group *spantypes.SpanMapperGroup) error
UpdateGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, group *spantypes.SpanMapperGroup) error
DeleteGroup(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
GetGroup(ctx context.Context, orgID, id valuer.UUID) (*spantypes.SpanMapperGroup, error)
CreateGroup(ctx context.Context, orgID valuer.UUID, group *spantypes.SpanMapperGroup) error
UpdateGroup(ctx context.Context, orgID, id valuer.UUID, group *spantypes.SpanMapperGroup) error
DeleteGroup(ctx context.Context, orgID, id valuer.UUID) error
// Mapper operations
ListMappers(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID) ([]*spantypes.SpanMapper, error)
GetMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID) (*spantypes.SpanMapper, error)
CreateMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, createdBy string, mapper *spantypes.SpanMapper) error
UpdateMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID, updatedBy string, mapper *spantypes.SpanMapper) error
DeleteMapper(ctx context.Context, orgID valuer.UUID, groupID valuer.UUID, id valuer.UUID) error
ListMappers(ctx context.Context, orgID, groupID valuer.UUID) ([]*spantypes.SpanMapper, error)
GetMapper(ctx context.Context, orgID, groupID, id valuer.UUID) (*spantypes.SpanMapper, error)
CreateMapper(ctx context.Context, orgID, groupID valuer.UUID, mapper *spantypes.SpanMapper) error
UpdateMapper(ctx context.Context, orgID, groupID, id valuer.UUID, mapper *spantypes.SpanMapper) error
DeleteMapper(ctx context.Context, orgID, groupID, id valuer.UUID) error
}
// Handler defines the HTTP handler interface for mapping group and mapper endpoints.

View File

@@ -120,7 +120,7 @@ func NewHandlers(
RegistryHandler: registryHandler,
RuleStateHistory: implrulestatehistory.NewHandler(modules.RuleStateHistory),
CloudIntegrationHandler: implcloudintegration.NewHandler(modules.CloudIntegration),
SpanMapperHandler: implspanmapper.NewHandler(nil, providerSettings), // todo(nitya): will update this in future PR
SpanMapperHandler: implspanmapper.NewHandler(modules.SpanMapper),
AlertmanagerHandler: signozalertmanager.NewHandler(alertmanagerService),
TraceDetail: impltracedetail.NewHandler(modules.TraceDetail),
RulerHandler: signozruler.NewHandler(rulerService),

View File

@@ -38,6 +38,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/services/implservices"
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/session/implsession"
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
"github.com/SigNoz/signoz/pkg/modules/spanmapper/implspanmapper"
"github.com/SigNoz/signoz/pkg/modules/spanpercentile"
"github.com/SigNoz/signoz/pkg/modules/spanpercentile/implspanpercentile"
"github.com/SigNoz/signoz/pkg/modules/tracedetail"
@@ -80,6 +82,7 @@ type Modules struct {
CloudIntegration cloudintegration.Module
RuleStateHistory rulestatehistory.Module
TraceDetail tracedetail.Module
SpanMapper spanmapper.Module
}
func NewModules(
@@ -133,5 +136,6 @@ func NewModules(
RuleStateHistory: implrulestatehistory.NewModule(implrulestatehistory.NewStore(telemetryStore, telemetryMetadataStore, providerSettings.Logger)),
CloudIntegration: cloudIntegrationModule,
TraceDetail: impltracedetail.NewModule(impltracedetail.NewTraceStore(telemetryStore), providerSettings, config.TraceDetail),
SpanMapper: implspanmapper.NewModule(implspanmapper.NewStore(sqlstore)),
}
}

View File

@@ -195,6 +195,7 @@ func NewSQLMigrationProviderFactories(
sqlmigration.NewServiceAccountAuthzactory(sqlstore),
sqlmigration.NewDropUserDeletedAtFactory(sqlstore, sqlschema),
sqlmigration.NewMigrateAWSAllRegionsFactory(sqlstore),
sqlmigration.NewAddSpanMapperFactory(sqlstore, sqlschema),
)
}

View File

@@ -0,0 +1,118 @@
package sqlmigration
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
)
type addSpanMapper struct {
sqlschema sqlschema.SQLSchema
sqlstore sqlstore.SQLStore
}
func NewAddSpanMapperFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_span_mapper"), func(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
return &addSpanMapper{sqlschema: sqlschema, sqlstore: sqlstore}, nil
})
}
func (migration *addSpanMapper) Register(migrations *migrate.Migrations) error {
return migrations.Register(migration.Up, migration.Down)
}
func (migration *addSpanMapper) Up(ctx context.Context, db *bun.DB) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
sqls := [][]byte{}
groupSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
Name: "span_mapper_group",
Columns: []*sqlschema.Column{
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "created_by", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "updated_by", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "org_id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "name", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "condition", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "enabled", DataType: sqlschema.DataTypeBoolean, Nullable: false, Default: "true"},
},
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
ColumnNames: []sqlschema.ColumnName{"id"},
},
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
{
ReferencingColumnName: sqlschema.ColumnName("org_id"),
ReferencedTableName: sqlschema.TableName("organizations"),
ReferencedColumnName: sqlschema.ColumnName("id"),
},
},
})
sqls = append(sqls, groupSQLs...)
groupIdxSQLs := migration.sqlschema.Operator().CreateIndex(
&sqlschema.UniqueIndex{
TableName: "span_mapper_group",
ColumnNames: []sqlschema.ColumnName{"org_id", "name"},
})
sqls = append(sqls, groupIdxSQLs...)
mapperSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
Name: "span_mapper",
Columns: []*sqlschema.Column{
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "created_by", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "updated_by", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "group_id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "name", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "field_context", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "config", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "enabled", DataType: sqlschema.DataTypeBoolean, Nullable: false, Default: "true"},
},
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
ColumnNames: []sqlschema.ColumnName{"id"},
},
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
{
ReferencingColumnName: sqlschema.ColumnName("group_id"),
ReferencedTableName: sqlschema.TableName("span_mapper_group"),
ReferencedColumnName: sqlschema.ColumnName("id"),
},
},
})
sqls = append(sqls, mapperSQLs...)
mapperIdxSQLs := migration.sqlschema.Operator().CreateIndex(
&sqlschema.UniqueIndex{
TableName: "span_mapper",
ColumnNames: []sqlschema.ColumnName{"group_id", "name"},
})
sqls = append(sqls, mapperIdxSQLs...)
for _, sql := range sqls {
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
return err
}
}
return tx.Commit()
}
func (migration *addSpanMapper) Down(context.Context, *bun.DB) error {
return nil
}

View File

@@ -1,6 +1,8 @@
package spantypes
import (
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -69,9 +71,9 @@ type PostableSpanMapper struct {
// UpdatableSpanMapper is the HTTP request body for updating a span mapper.
// All fields are optional; only non-nil fields are applied.
type UpdatableSpanMapper struct {
FieldContext FieldContext `json:"field_context,omitempty"`
Config *SpanMapperConfig `json:"config,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
FieldContext FieldContext `json:"field_context"`
Config *SpanMapperConfig `json:"config"`
Enabled *bool `json:"enabled"`
}
type GettableSpanMapper = SpanMapper
@@ -88,7 +90,53 @@ func (SpanMapperOperation) Enum() []any {
return []any{SpanMapperOperationMove, SpanMapperOperationCopy}
}
func NewSpanMapperFromStorable(s *StorableSpanMapper) *SpanMapper {
func NewSpanMapper(groupID valuer.UUID, createdBy string, p *PostableSpanMapper) *SpanMapper {
now := time.Now()
return &SpanMapper{
ID: valuer.GenerateUUID(),
GroupID: groupID,
Name: p.Name,
FieldContext: p.FieldContext,
Config: p.Config,
Enabled: p.Enabled,
TimeAuditable: types.TimeAuditable{
CreatedAt: now,
UpdatedAt: now,
},
UserAuditable: types.UserAuditable{
CreatedBy: createdBy,
UpdatedBy: createdBy,
},
}
}
func (m *SpanMapper) Update(u *UpdatableSpanMapper, updatedBy string) {
m.FieldContext = u.FieldContext
if u.Config != nil {
m.Config = *u.Config
}
if u.Enabled != nil {
m.Enabled = *u.Enabled
}
m.UpdatedAt = time.Now()
m.UpdatedBy = updatedBy
}
func (m *SpanMapper) ToStorable() *StorableSpanMapper {
return &StorableSpanMapper{
Identifiable: types.Identifiable{ID: m.ID},
TimeAuditable: m.TimeAuditable,
UserAuditable: m.UserAuditable,
GroupID: m.GroupID,
Name: m.Name,
FieldContext: m.FieldContext,
Config: m.Config,
Enabled: m.Enabled,
}
}
func (s *StorableSpanMapper) ToSpanMapper() *SpanMapper {
return &SpanMapper{
TimeAuditable: s.TimeAuditable,
UserAuditable: s.UserAuditable,
@@ -101,33 +149,10 @@ func NewSpanMapperFromStorable(s *StorableSpanMapper) *SpanMapper {
}
}
func NewSpanMapperFromPostable(req *PostableSpanMapper) *SpanMapper {
return &SpanMapper{
Name: req.Name,
FieldContext: req.FieldContext,
Config: req.Config,
Enabled: req.Enabled,
}
}
func NewSpanMapperFromUpdatable(req *UpdatableSpanMapper) *SpanMapper {
m := &SpanMapper{}
if req.FieldContext != (FieldContext{}) {
m.FieldContext = req.FieldContext
}
if req.Config != nil {
m.Config = *req.Config
}
if req.Enabled != nil {
m.Enabled = *req.Enabled
}
return m
}
func NewSpanMappersFromStorableSpanMappers(ss []*StorableSpanMapper) []*SpanMapper {
func NewSpanMappersFromStorable(ss []*StorableSpanMapper) []*SpanMapper {
mappers := make([]*SpanMapper, len(ss))
for i, s := range ss {
mappers[i] = NewSpanMapperFromStorable(s)
mappers[i] = s.ToSpanMapper()
}
return mappers
}

View File

@@ -1,6 +1,8 @@
package spantypes
import (
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -11,9 +13,6 @@ var (
ErrCodeMappingGroupAlreadyExists = errors.MustNewCode("span_attribute_mapping_group_already_exists")
)
// SpanMapperGroupCategory defaults will be llm, tool, agent but user can configure more as they want.
type SpanMapperGroupCategory valuer.String
// A group runs when any of the listed attribute/resource key patterns match.
type SpanMapperGroupCondition struct {
Attributes []string `json:"attributes" required:"true" nullable:"true"`
@@ -28,7 +27,6 @@ type SpanMapperGroup struct {
ID valuer.UUID `json:"id" required:"true"`
OrgID valuer.UUID `json:"orgId" required:"true"`
Name string `json:"name" required:"true"`
Category SpanMapperGroupCategory `json:"category" required:"true"`
Condition SpanMapperGroupCondition `json:"condition" required:"true"`
Enabled bool `json:"enabled" required:"true"`
}
@@ -38,7 +36,6 @@ type GettableSpanMapperGroup = SpanMapperGroup
type PostableSpanMapperGroup struct {
Name string `json:"name" required:"true"`
Category SpanMapperGroupCategory `json:"category" required:"true"`
Condition SpanMapperGroupCondition `json:"condition" required:"true"`
Enabled bool `json:"enabled"`
}
@@ -46,44 +43,39 @@ type PostableSpanMapperGroup struct {
// UpdatableSpanMapperGroup is the HTTP request body for updating a mapping group.
// All fields are optional; only non-nil fields are applied.
type UpdatableSpanMapperGroup struct {
Name *string `json:"name,omitempty"`
Condition *SpanMapperGroupCondition `json:"condition,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
Name *string `json:"name" nullable:"true"`
Condition *SpanMapperGroupCondition `json:"condition" nullable:"true"`
Enabled *bool `json:"enabled" nullable:"true"`
}
type ListSpanMapperGroupsQuery struct {
Category *SpanMapperGroupCategory `query:"category"`
Enabled *bool `query:"enabled"`
Enabled *bool `query:"enabled"`
}
type GettableSpanMapperGroups struct {
Items []*GettableSpanMapperGroup `json:"items" required:"true" nullable:"false"`
}
func NewSpanMapperGroupFromStorable(s *StorableSpanMapperGroup) *SpanMapperGroup {
return &SpanMapperGroup{
TimeAuditable: s.TimeAuditable,
UserAuditable: s.UserAuditable,
ID: s.ID,
OrgID: s.OrgID,
Name: s.Name,
Category: s.Category,
Condition: s.Condition,
Enabled: s.Enabled,
}
}
func NewSpanMapperGroupFromPostable(p *PostableSpanMapperGroup) *SpanMapperGroup {
func NewSpanMapperGroup(orgID valuer.UUID, createdBy string, p *PostableSpanMapperGroup) *SpanMapperGroup {
now := time.Now()
return &SpanMapperGroup{
ID: valuer.GenerateUUID(),
OrgID: orgID,
Name: p.Name,
Category: p.Category,
Condition: p.Condition,
Enabled: p.Enabled,
TimeAuditable: types.TimeAuditable{
CreatedAt: now,
UpdatedAt: now,
},
UserAuditable: types.UserAuditable{
CreatedBy: createdBy,
UpdatedBy: createdBy,
},
}
}
func NewSpanMapperGroupFromUpdatable(u *UpdatableSpanMapperGroup) *SpanMapperGroup {
g := &SpanMapperGroup{}
func (g *SpanMapperGroup) Update(u *UpdatableSpanMapperGroup, updatedBy string) {
if u.Name != nil {
g.Name = *u.Name
}
@@ -93,13 +85,38 @@ func NewSpanMapperGroupFromUpdatable(u *UpdatableSpanMapperGroup) *SpanMapperGro
if u.Enabled != nil {
g.Enabled = *u.Enabled
}
return g
g.UpdatedAt = time.Now()
g.UpdatedBy = updatedBy
}
func NewSpanMapperGroupsFromStorableGroups(ss []*StorableSpanMapperGroup) []*SpanMapperGroup {
func (g *SpanMapperGroup) ToStorable() *StorableSpanMapperGroup {
return &StorableSpanMapperGroup{
Identifiable: types.Identifiable{ID: g.ID},
TimeAuditable: g.TimeAuditable,
UserAuditable: g.UserAuditable,
OrgID: g.OrgID,
Name: g.Name,
Condition: g.Condition,
Enabled: g.Enabled,
}
}
func (s *StorableSpanMapperGroup) ToSpanMapperGroup() *SpanMapperGroup {
return &SpanMapperGroup{
TimeAuditable: s.TimeAuditable,
UserAuditable: s.UserAuditable,
ID: s.ID,
OrgID: s.OrgID,
Name: s.Name,
Condition: s.Condition,
Enabled: s.Enabled,
}
}
func NewSpanMapperGroupsFromStorable(ss []*StorableSpanMapperGroup) []*SpanMapperGroup {
groups := make([]*SpanMapperGroup, len(ss))
for i, s := range ss {
groups[i] = NewSpanMapperGroupFromStorable(s)
groups[i] = s.ToSpanMapperGroup()
}
return groups
}

View File

@@ -19,7 +19,6 @@ type StorableSpanMapperGroup struct {
OrgID valuer.UUID `bun:"org_id,type:text,notnull"`
Name string `bun:"name,type:text,notnull"`
Category SpanMapperGroupCategory `bun:"category,type:text,notnull"`
Condition SpanMapperGroupCondition `bun:"condition,type:jsonb,notnull"`
Enabled bool `bun:"enabled,notnull,default:true"`
}

View File

@@ -6,18 +6,18 @@ import (
"github.com/SigNoz/signoz/pkg/valuer"
)
type Store interface {
type SpanMapperStore interface {
// Group operations
ListSpanMapperGroups(ctx context.Context, orgID valuer.UUID, q *ListSpanMapperGroupsQuery) ([]*StorableSpanMapperGroup, error)
GetSpanMapperGroup(ctx context.Context, orgID, id valuer.UUID) (*StorableSpanMapperGroup, error)
CreateSpanMapperGroup(ctx context.Context, group *StorableSpanMapperGroup) error
UpdateSpanMapperGroup(ctx context.Context, group *StorableSpanMapperGroup) error
DeleteSpanMapperGroup(ctx context.Context, orgID, id valuer.UUID) error
ListGroups(ctx context.Context, orgID valuer.UUID, q *ListSpanMapperGroupsQuery) ([]*SpanMapperGroup, error)
GetGroup(ctx context.Context, orgID, id valuer.UUID) (*SpanMapperGroup, error)
CreateGroup(ctx context.Context, group *SpanMapperGroup) error
UpdateGroup(ctx context.Context, group *SpanMapperGroup) error
DeleteGroup(ctx context.Context, orgID, id valuer.UUID) error
// Mapper operations
ListSpanMappers(ctx context.Context, orgID, groupID valuer.UUID) ([]*StorableSpanMapper, error)
GetSpanMapper(ctx context.Context, orgID, groupID, id valuer.UUID) (*StorableSpanMapper, error)
CreateSpanMapper(ctx context.Context, mapper *StorableSpanMapper) error
UpdateSpanMapper(ctx context.Context, mapper *StorableSpanMapper) error
DeleteSpanMapper(ctx context.Context, orgID, groupID, id valuer.UUID) error
ListMappers(ctx context.Context, orgID, groupID valuer.UUID) ([]*SpanMapper, error)
GetMapper(ctx context.Context, orgID, groupID, id valuer.UUID) (*SpanMapper, error)
CreateMapper(ctx context.Context, mapper *SpanMapper) error
UpdateMapper(ctx context.Context, mapper *SpanMapper) error
DeleteMapper(ctx context.Context, orgID, groupID, id valuer.UUID) error
}