feat(organization): schema changes for the organizations entity (#7684)

* feat(organization): add hname and alias for organization

* fix: boolean values are not shown in the list panel's column

* fix: moved logic to component level

* fix: added type

* fix: added test cases

* fix: added test cases

* chore: update copy webpack plugin

* Revert "fix: display same key with multiple data types in filter suggestions by enhancing the deduping logic (#7255)"

This reverts commit 1e85981a17a8e715e948308d3e85072d976907d3.

* fix: use query search v2 for traces data source to handle multiple data types for the same key

* fix(QueryBuilderSearchV2): add user typed option if it doesn't exist in the payload

* fix(QueryBuilderSearchV2): increase the height of search dropdown for non-logs data sources

* fix: display span scope selector for trace data source

* chore: remove the span scope selector from qb search v1 and move the component to search v2

* fix: write test to ensure that we display span scope selector for traces data source

* fix: limit converting  ->   only to log data source

* fix: don't display empty suggestion if only spaces are typed

* chore: tests for span scope selector

* chore: qb search flow (key, operator, value) test cases

* refactor: fix the Maximum update depth reached issue while running tests

* chore: overall improvements to span scope selector tests

Resource attr filter: style fix and quick filter changes (#7691)

* chore: resource attr filter init

* chore: resource attr filter api integration

* chore: operator config updated

* chore: fliter show hide logic and styles

* chore: add support for custom operator list to qb

* chore: minor refactor

* chore: minor code refactor

* test: quick filters test suite added

* test: quick filters test suite added

* test: all errors test suite added

* chore: style fix

* test: all errors mock fix

* chore: test case fix and mixpanel update

* chore: color update

* chore: minor refactor

* chore: style fix

* chore: set default query in exceptions tab

* chore: style fix

* chore: minor refactor

* chore: minor refactor

* chore: minor refactor

* chore: test update

* chore: fix filter header with no query name

* fix: scroll fix

* chore: add data source traces to quick filters

* chore: replace div with fragment

---------

Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local>

fix: handle rate operators for table panel (#7695)

* fix: handle rate operators for table panel

chore: fix error rate (#7701)

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* feat(organization): minor cleanups

* feat(organization): better naming for api and usecase

* feat(organization): better packaging for modules

* feat(organization): change hname to displayName

* feat(organization): update the migration to use dialect

* feat(organization): update the migration to use dialect

* feat(organization): update the migration to use dialect

* feat(organization): revert back to impl

* feat(organization): remove DI from organization

* feat(organization): address review comments

* feat(organization): address review comments

* feat(organization): address review comments

---------

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
This commit is contained in:
Vikrant Gupta 2025-04-25 19:38:15 +05:30 committed by GitHub
parent a1846c008a
commit c322657666
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 690 additions and 378 deletions

View File

@ -12,6 +12,7 @@ import (
"github.com/SigNoz/signoz/ee/query-service/usage" "github.com/SigNoz/signoz/ee/query-service/usage"
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/apis/fields" "github.com/SigNoz/signoz/pkg/apis/fields"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/preference" "github.com/SigNoz/signoz/pkg/modules/preference"
preferencecore "github.com/SigNoz/signoz/pkg/modules/preference/core" preferencecore "github.com/SigNoz/signoz/pkg/modules/preference/core"
baseapp "github.com/SigNoz/signoz/pkg/query-service/app" baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
@ -59,6 +60,8 @@ type APIHandler struct {
// NewAPIHandler returns an APIHandler // NewAPIHandler returns an APIHandler
func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, error) { func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, error) {
preference := preference.NewAPI(preferencecore.NewPreference(preferencecore.NewStore(signoz.SQLStore), preferencetypes.NewDefaultPreferenceMap())) preference := preference.NewAPI(preferencecore.NewPreference(preferencecore.NewStore(signoz.SQLStore), preferencetypes.NewDefaultPreferenceMap()))
organizationAPI := implorganization.NewAPI(implorganization.NewModule(implorganization.NewStore(signoz.SQLStore)))
organizationModule := implorganization.NewModule(implorganization.NewStore(signoz.SQLStore))
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{ baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
Reader: opts.DataConnector, Reader: opts.DataConnector,
@ -78,6 +81,8 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
FieldsAPI: fields.NewAPI(signoz.TelemetryStore), FieldsAPI: fields.NewAPI(signoz.TelemetryStore),
Signoz: signoz, Signoz: signoz,
Preference: preference, Preference: preference,
OrganizationAPI: organizationAPI,
OrganizationModule: organizationModule,
}) })
if err != nil { if err != nil {

View File

@ -134,7 +134,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
return return
} }
_, registerError := baseauth.Register(ctx, req, ah.Signoz.Alertmanager) _, registerError := baseauth.Register(ctx, req, ah.Signoz.Alertmanager, ah.OrganizationModule)
if !registerError.IsNil() { if !registerError.IsNil() {
RespondError(w, apierr, nil) RespondError(w, apierr, nil)
return return
@ -151,9 +151,8 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
func (ah *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) { func (ah *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) {
token := mux.Vars(r)["token"] token := mux.Vars(r)["token"]
sourceUrl := r.URL.Query().Get("ref") sourceUrl := r.URL.Query().Get("ref")
ctx := context.Background()
inviteObject, err := baseauth.GetInvite(context.Background(), token) inviteObject, err := baseauth.GetInvite(r.Context(), token, ah.OrganizationModule)
if err != nil { if err != nil {
RespondError(w, model.BadRequest(err), nil) RespondError(w, model.BadRequest(err), nil)
return return
@ -163,7 +162,7 @@ func (ah *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) {
InvitationResponseObject: inviteObject, InvitationResponseObject: inviteObject,
} }
precheck, apierr := ah.AppDao().PrecheckLogin(ctx, inviteObject.Email, sourceUrl) precheck, apierr := ah.AppDao().PrecheckLogin(r.Context(), inviteObject.Email, sourceUrl)
resp.Precheck = precheck resp.Precheck = precheck
if apierr != nil { if apierr != nil {

View File

@ -9,10 +9,9 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/pkg/errors"
"github.com/SigNoz/signoz/ee/query-service/constants" "github.com/SigNoz/signoz/ee/query-service/constants"
"github.com/SigNoz/signoz/ee/query-service/model" "github.com/SigNoz/signoz/ee/query-service/model"
"github.com/pkg/errors"
) )
var C *Client var C *Client

View File

@ -138,15 +138,6 @@ func (lm *Manager) UploadUsage() {
zap.L().Info("uploading usage data") zap.L().Info("uploading usage data")
orgName := ""
orgNames, orgError := lm.modelDao.GetOrgs(ctx)
if orgError != nil {
zap.L().Error("failed to get org data: %v", zap.Error(orgError))
}
if len(orgNames) == 1 {
orgName = orgNames[0].Name
}
usagesPayload := []model.Usage{} usagesPayload := []model.Usage{}
for _, usage := range usages { for _, usage := range usages {
usageDataBytes, err := encryption.Decrypt([]byte(usage.ExporterID[:32]), []byte(usage.Data)) usageDataBytes, err := encryption.Decrypt([]byte(usage.ExporterID[:32]), []byte(usage.Data))
@ -166,7 +157,7 @@ func (lm *Manager) UploadUsage() {
usageData.ExporterID = usage.ExporterID usageData.ExporterID = usage.ExporterID
usageData.Type = usage.Type usageData.Type = usage.Type
usageData.Tenant = "default" usageData.Tenant = "default"
usageData.OrgName = orgName usageData.OrgName = "default"
usageData.TenantId = lm.tenantID usageData.TenantId = lm.tenantID
usagesPayload = append(usagesPayload, usageData) usagesPayload = append(usagesPayload, usageData)
} }

View File

@ -79,6 +79,14 @@ func (dialect *dialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB,
} }
func (dialect *dialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error { func (dialect *dialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error {
columnExists, err := dialect.ColumnExists(ctx, bun, table, column)
if err != nil {
return err
}
if !columnExists {
return nil
}
columnType, err := dialect.GetColumnType(ctx, bun, table, column) columnType, err := dialect.GetColumnType(ctx, bun, table, column)
if err != nil { if err != nil {
return err return err
@ -151,6 +159,26 @@ func (dialect *dialect) ColumnExists(ctx context.Context, bun bun.IDB, table str
return count > 0, nil return count > 0, nil
} }
func (dialect *dialect) AddColumn(ctx context.Context, bun bun.IDB, table string, column string, columnExpr string) error {
exists, err := dialect.ColumnExists(ctx, bun, table, column)
if err != nil {
return err
}
if !exists {
_, err = bun.
NewAddColumn().
Table(table).
ColumnExpr(column + " " + columnExpr).
Exec(ctx)
if err != nil {
return err
}
}
return nil
}
func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table string, oldColumnName string, newColumnName string) (bool, error) { func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table string, oldColumnName string, newColumnName string) (bool, error) {
oldColumnExists, err := dialect.ColumnExists(ctx, bun, table, oldColumnName) oldColumnExists, err := dialect.ColumnExists(ctx, bun, table, oldColumnName)
if err != nil { if err != nil {
@ -162,10 +190,14 @@ func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table str
return false, err return false, err
} }
if !oldColumnExists && newColumnExists { if newColumnExists {
return true, nil return true, nil
} }
if !oldColumnExists {
return false, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, fmt.Sprintf("old column: %s doesn't exist", oldColumnName))
}
_, err = bun. _, err = bun.
ExecContext(ctx, "ALTER TABLE "+table+" RENAME COLUMN "+oldColumnName+" TO "+newColumnName) ExecContext(ctx, "ALTER TABLE "+table+" RENAME COLUMN "+oldColumnName+" TO "+newColumnName)
if err != nil { if err != nil {
@ -174,6 +206,26 @@ func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table str
return true, nil return true, nil
} }
func (dialect *dialect) DropColumn(ctx context.Context, bun bun.IDB, table string, column string) error {
exists, err := dialect.ColumnExists(ctx, bun, table, column)
if err != nil {
return err
}
if exists {
_, err = bun.
NewDropColumn().
Table(table).
Column(column).
Exec(ctx)
if err != nil {
return err
}
}
return nil
}
func (dialect *dialect) TableExists(ctx context.Context, bun bun.IDB, table interface{}) (bool, error) { func (dialect *dialect) TableExists(ctx context.Context, bun bun.IDB, table interface{}) (bool, error) {
count := 0 count := 0

View File

@ -64,7 +64,7 @@ function App(): JSX.Element {
// wait for the required data to be loaded before doing init for anything! // wait for the required data to be loaded before doing init for anything!
if (!isFetchingActiveLicenseV3 && activeLicenseV3 && org) { if (!isFetchingActiveLicenseV3 && activeLicenseV3 && org) {
const orgName = const orgName =
org && Array.isArray(org) && org.length > 0 ? org[0].name : ''; org && Array.isArray(org) && org.length > 0 ? org[0].displayName : '';
const { name, email, role } = user; const { name, email, role } = user;

View File

@ -1,4 +1,4 @@
import axios from 'api'; import { ApiV2Instance as axios } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api'; import { ErrorResponse, SuccessResponse } from 'types/api';
@ -8,14 +8,12 @@ const editOrg = async (
props: Props, props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => { ): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try { try {
const response = await axios.put(`/org/${props.orgId}`, { const response = await axios.put(`/orgs/me`, {
name: props.name, displayName: props.displayName,
isAnonymous: props.isAnonymous,
hasOptedUpdates: props.hasOptedUpdates,
}); });
return { return {
statusCode: 200, statusCode: 204,
error: null, error: null,
message: response.data.status, message: response.data.status,
payload: response.data, payload: response.data,

View File

@ -160,7 +160,7 @@ export default function CustomDomainSettings(): JSX.Element {
{!isLoadingDeploymentsData && ( {!isLoadingDeploymentsData && (
<Card className="custom-domain-settings-card"> <Card className="custom-domain-settings-card">
<div className="custom-domain-settings-content-header"> <div className="custom-domain-settings-content-header">
Team {org?.[0]?.name} Information Team {org?.[0]?.displayName} Information
</div> </div>
<div className="custom-domain-settings-content-body"> <div className="custom-domain-settings-content-body">

View File

@ -13,8 +13,7 @@ import { useTranslation } from 'react-i18next';
export interface OrgData { export interface OrgData {
id: string; id: string;
isAnonymous: boolean; displayName: string;
name: string;
} }
export interface OrgDetails { export interface OrgDetails {
@ -110,15 +109,14 @@ function OrgQuestions({
try { try {
setIsLoading(true); setIsLoading(true);
const { statusCode, error } = await editOrg({ const { statusCode, error } = await editOrg({
isAnonymous: currentOrgData.isAnonymous, displayName: organisationName,
name: organisationName,
orgId: currentOrgData.id, orgId: currentOrgData.id,
}); });
if (statusCode === 200) { if (statusCode === 204) {
updateOrg(currentOrgData?.id, orgDetails.organisationName); updateOrg(currentOrgData?.id, organisationName);
logEvent('Org Onboarding: Org Name Updated', { logEvent('Org Onboarding: Org Name Updated', {
organisationName: orgDetails.organisationName, organisationName,
}); });
logEvent('Org Onboarding: Answered', { logEvent('Org Onboarding: Answered', {

View File

@ -94,7 +94,7 @@ function OnboardingQuestionaire(): JSX.Element {
setOrgDetails({ setOrgDetails({
...orgDetails, ...orgDetails,
organisationName: org[0].name, organisationName: org[0].displayName,
}); });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps

View File

@ -390,7 +390,7 @@ function OnboardingAddDataSource(): JSX.Element {
setSetupStepItems([ setSetupStepItems([
{ {
...setupStepItemsBase[0], ...setupStepItemsBase[0],
description: org?.[0]?.name || '', description: org?.[0]?.displayName || '',
}, },
...setupStepItemsBase.slice(1), ...setupStepItemsBase.slice(1),
]); ]);
@ -403,7 +403,7 @@ function OnboardingAddDataSource(): JSX.Element {
setSetupStepItems([ setSetupStepItems([
{ {
...setupStepItemsBase[0], ...setupStepItemsBase[0],
description: org?.[0]?.name || '', description: org?.[0]?.displayName || '',
}, },
{ {
...setupStepItemsBase[1], ...setupStepItemsBase[1],
@ -415,7 +415,7 @@ function OnboardingAddDataSource(): JSX.Element {
setSetupStepItems([ setSetupStepItems([
{ {
...setupStepItemsBase[0], ...setupStepItemsBase[0],
description: org?.[0]?.name || '', description: org?.[0]?.displayName || '',
}, },
{ {
...setupStepItemsBase[1], ...setupStepItemsBase[1],

View File

@ -7,27 +7,22 @@ import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { requireErrorMessage } from 'utils/form/requireErrorMessage'; import { requireErrorMessage } from 'utils/form/requireErrorMessage';
function DisplayName({ function DisplayName({ index, id: orgId }: DisplayNameProps): JSX.Element {
index,
id: orgId,
isAnonymous,
}: DisplayNameProps): JSX.Element {
const [form] = Form.useForm<FormValues>(); const [form] = Form.useForm<FormValues>();
const orgName = Form.useWatch('name', form); const orgName = Form.useWatch('displayName', form);
const { t } = useTranslation(['organizationsettings', 'common']); const { t } = useTranslation(['organizationsettings', 'common']);
const { org, updateOrg } = useAppContext(); const { org, updateOrg } = useAppContext();
const { name } = (org || [])[index]; const { displayName } = (org || [])[index];
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const { notifications } = useNotifications(); const { notifications } = useNotifications();
const onSubmit = async (values: FormValues): Promise<void> => { const onSubmit = async (values: FormValues): Promise<void> => {
try { try {
setIsLoading(true); setIsLoading(true);
const { name } = values; const { displayName } = values;
const { statusCode, error } = await editOrg({ const { statusCode, error } = await editOrg({
isAnonymous, displayName,
name,
orgId, orgId,
}); });
if (statusCode === 200) { if (statusCode === 200) {
@ -36,7 +31,7 @@ function DisplayName({
ns: 'common', ns: 'common',
}), }),
}); });
updateOrg(orgId, name); updateOrg(orgId, displayName);
} else { } else {
notifications.error({ notifications.error({
message: message:
@ -61,18 +56,18 @@ function DisplayName({
return <div />; return <div />;
} }
const isDisabled = isLoading || orgName === name || !orgName; const isDisabled = isLoading || orgName === displayName || !orgName;
return ( return (
<Form <Form
initialValues={{ name }} initialValues={{ displayName }}
form={form} form={form}
layout="vertical" layout="vertical"
onFinish={onSubmit} onFinish={onSubmit}
autoComplete="off" autoComplete="off"
> >
<Form.Item <Form.Item
name="name" name="displayName"
label="Display name" label="Display name"
rules={[{ required: true, message: requireErrorMessage('Display name') }]} rules={[{ required: true, message: requireErrorMessage('Display name') }]}
> >
@ -95,11 +90,10 @@ function DisplayName({
interface DisplayNameProps { interface DisplayNameProps {
index: number; index: number;
id: IUser['id']; id: IUser['id'];
isAnonymous: boolean;
} }
interface FormValues { interface FormValues {
name: string; displayName: string;
} }
export default DisplayName; export default DisplayName;

View File

@ -23,12 +23,7 @@ function OrganizationSettings(): JSX.Element {
<> <>
<Space direction="vertical"> <Space direction="vertical">
{org.map((e, index) => ( {org.map((e, index) => (
<DisplayName <DisplayName key={e.id} id={e.id} index={index} />
isAnonymous={e.isAnonymous}
key={e.id}
id={e.id}
index={index}
/>
))} ))}
</Space> </Space>
<Divider /> <Divider />

View File

@ -1,4 +1,4 @@
import { Button, Form, Input, Space, Switch, Typography } from 'antd'; import { Button, Form, Input, Typography } from 'antd';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import getInviteDetails from 'api/user/getInviteDetails'; import getInviteDetails from 'api/user/getInviteDetails';
import loginApi from 'api/user/login'; import loginApi from 'api/user/login';
@ -14,13 +14,7 @@ import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { PayloadProps as LoginPrecheckPayloadProps } from 'types/api/user/loginPrecheck'; import { PayloadProps as LoginPrecheckPayloadProps } from 'types/api/user/loginPrecheck';
import { import { ButtonContainer, FormContainer, FormWrapper, Label } from './styles';
ButtonContainer,
FormContainer,
FormWrapper,
Label,
MarginTop,
} from './styles';
import { isPasswordNotValidMessage, isPasswordValid } from './utils'; import { isPasswordNotValidMessage, isPasswordValid } from './utils';
const { Title } = Typography; const { Title } = Typography;
@ -111,24 +105,15 @@ function SignUp({ version }: SignUpProps): JSX.Element {
const isPreferenceVisible = token === null; const isPreferenceVisible = token === null;
const commonHandler = async ( const commonHandler = async (values: FormValues): Promise<void> => {
values: FormValues,
isPreferenceVisible: boolean,
): Promise<void> => {
try { try {
const { organizationName, password, firstName, email } = values; const { organizationName, password, firstName, email } = values;
const response = await signUpApi({ const response = await signUpApi({
email, email,
name: firstName, name: firstName,
orgName: organizationName, orgDisplayName: organizationName,
password, password,
token: params.get('token') || undefined, token: params.get('token') || undefined,
...(isPreferenceVisible
? {
isAnonymous: values.isAnonymous,
hasOptedUpdates: values.hasOptedUpdates,
}
: {}),
}); });
if (response.statusCode === 200) { if (response.statusCode === 200) {
@ -171,7 +156,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
const response = await signUpApi({ const response = await signUpApi({
email: values.email, email: values.email,
name: values.firstName, name: values.firstName,
orgName: values.organizationName, orgDisplayName: values.organizationName,
password: values.password, password: values.password,
token: params.get('token') || undefined, token: params.get('token') || undefined,
sourceUrl: encodeURIComponent(window.location.href), sourceUrl: encodeURIComponent(window.location.href),
@ -221,14 +206,14 @@ function SignUp({ version }: SignUpProps): JSX.Element {
} }
if (isPreferenceVisible) { if (isPreferenceVisible) {
await commonHandler(values, true); await commonHandler(values);
} else { } else {
logEvent('Account Created Successfully', { logEvent('Account Created Successfully', {
email: values.email, email: values.email,
name: values.firstName, name: values.firstName,
}); });
await commonHandler(values, false); await commonHandler(values);
} }
setLoading(false); setLoading(false);
@ -278,7 +263,6 @@ function SignUp({ version }: SignUpProps): JSX.Element {
<FormContainer <FormContainer
onFinish={!precheck.sso ? handleSubmit : handleSubmitSSO} onFinish={!precheck.sso ? handleSubmit : handleSubmitSSO}
onValuesChange={handleValuesChange} onValuesChange={handleValuesChange}
initialValues={{ hasOptedUpdates: true, isAnonymous: false }}
form={form} form={form}
> >
<Title level={4}>Create your account</Title> <Title level={4}>Create your account</Title>
@ -359,34 +343,6 @@ function SignUp({ version }: SignUpProps): JSX.Element {
)} )}
</div> </div>
)} )}
{isPreferenceVisible && (
<>
<MarginTop marginTop="2.4375rem">
<Space>
<FormContainer.Item
noStyle
name="hasOptedUpdates"
valuePropName="checked"
>
<Switch />
</FormContainer.Item>
<Typography>{t('prompt_keepme_posted')} </Typography>
</Space>
</MarginTop>
<MarginTop marginTop="0.5rem">
<Space>
<FormContainer.Item noStyle name="isAnonymous" valuePropName="checked">
<Switch />
</FormContainer.Item>
<Typography>{t('prompt_anonymise')}</Typography>
</Space>
</MarginTop>
</>
)}
{isPreferenceVisible && ( {isPreferenceVisible && (
<Typography.Paragraph <Typography.Paragraph
italic italic

View File

@ -82,10 +82,8 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
return [ return [
{ {
createdAt: 0, createdAt: 0,
hasOptedUpdates: false,
id: userData.payload.orgId, id: userData.payload.orgId,
isAnonymous: false, displayName: userData.payload.organization,
name: userData.payload.organization,
}, },
]; ];
} }
@ -95,10 +93,8 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
...prev.slice(0, orgIndex), ...prev.slice(0, orgIndex),
{ {
createdAt: 0, createdAt: 0,
hasOptedUpdates: false,
id: userData.payload.orgId, id: userData.payload.orgId,
isAnonymous: false, displayName: userData.payload.organization,
name: userData.payload.organization,
}, },
...prev.slice(orgIndex + 1, prev.length), ...prev.slice(orgIndex + 1, prev.length),
]; ];
@ -209,10 +205,8 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
...org.slice(0, orgIndex), ...org.slice(0, orgIndex),
{ {
createdAt: 0, createdAt: 0,
hasOptedUpdates: false,
id: orgId, id: orgId,
isAnonymous: false, displayName: updatedOrgName,
name: updatedOrgName,
}, },
...org.slice(orgIndex + 1, org.length), ...org.slice(orgIndex + 1, org.length),
]; ];

View File

@ -156,10 +156,8 @@ export function getAppContextMock(
org: [ org: [
{ {
createdAt: 0, createdAt: 0,
hasOptedUpdates: false,
id: 'does-not-matter-id', id: 'does-not-matter-id',
isAnonymous: false, displayName: 'Pentagon',
name: 'Pentagon',
}, },
], ],
isFetchingUser: false, isFetchingUser: false,

View File

@ -1,8 +1,6 @@
export interface Props { export interface Props {
name: string; displayName: string;
isAnonymous: boolean;
orgId: string; orgId: string;
hasOptedUpdates?: boolean;
} }
export interface PayloadProps { export interface PayloadProps {

View File

@ -14,6 +14,6 @@ export interface PayloadProps {
name: User['name']; name: User['name'];
role: ROLES; role: ROLES;
token: string; token: string;
organization: Organization['name']; organization: Organization['displayName'];
precheck?: LoginPrecheckPayloadProps; precheck?: LoginPrecheckPayloadProps;
} }

View File

@ -1,9 +1,7 @@
export interface Organization { export interface Organization {
createdAt: number; createdAt: number;
hasOptedUpdates: boolean;
id: string; id: string;
isAnonymous: boolean; displayName: string;
name: string;
} }
export type PayloadProps = Organization[]; export type PayloadProps = Organization[];

View File

@ -1,10 +1,8 @@
export interface Props { export interface Props {
name: string; name: string;
orgName: string; orgDisplayName: string;
email: string; email: string;
password: string; password: string;
token?: string; token?: string;
sourceUrl?: string; sourceUrl?: string;
isAnonymous?: boolean;
hasOptedUpdates?: boolean;
} }

View File

@ -2,6 +2,8 @@ export type Created = 201;
export type Success = 200; export type Success = 200;
export type SuccessNoContent = 204;
export type Forbidden = 403; export type Forbidden = 403;
export type BadRequest = 400; export type BadRequest = 400;
@ -14,7 +16,7 @@ export type Conflict = 409;
export type ServerError = 500; export type ServerError = 500;
export type SuccessStatusCode = Created | Success; export type SuccessStatusCode = Created | Success | SuccessNoContent;
export type ErrorStatusCode = export type ErrorStatusCode =
| Forbidden | Forbidden

View File

@ -33,7 +33,7 @@ export const loginApi = async (page: Page): Promise<void> => {
body: JSON.stringify(loginApiResponse), body: JSON.stringify(loginApiResponse),
}), }),
), ),
page.route(`**/org/${userLoginResponse.orgId}`, (route) => page.route(`**/orgs/me`, (route) =>
route.fulfill({ route.fulfill({
status: 200, status: 200,
body: JSON.stringify(updateOrgResponse), body: JSON.stringify(updateOrgResponse),

View File

@ -0,0 +1,80 @@
package implorganization
import (
"encoding/json"
"net/http"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type organizationAPI struct {
module organization.Module
}
func NewAPI(module organization.Module) organization.API {
return &organizationAPI{module: module}
}
func (api *organizationAPI) Get(rw http.ResponseWriter, r *http.Request) {
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(rw, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "unauthenticated"))
return
}
orgID, err := valuer.NewUUID(claims.OrgID)
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid org id"))
return
}
organization, err := api.module.Get(r.Context(), orgID)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, organization)
}
func (api *organizationAPI) GetAll(rw http.ResponseWriter, r *http.Request) {
organizations, err := api.module.GetAll(r.Context())
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, organizations)
}
func (api *organizationAPI) Update(rw http.ResponseWriter, r *http.Request) {
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(rw, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "unauthenticated"))
return
}
orgID, err := valuer.NewUUID(claims.OrgID)
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid org id"))
return
}
var req *types.Organization
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
render.Error(rw, err)
}
req.ID = orgID
err = api.module.Update(r.Context(), req)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}

View File

@ -0,0 +1,33 @@
package implorganization
import (
"context"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
type organizationModule struct {
store types.OrganizationStore
}
func NewModule(organizationStore types.OrganizationStore) organization.Module {
return &organizationModule{store: organizationStore}
}
func (o *organizationModule) Create(ctx context.Context, organization *types.Organization) error {
return o.store.Create(ctx, organization)
}
func (o *organizationModule) Get(ctx context.Context, id valuer.UUID) (*types.Organization, error) {
return o.store.Get(ctx, id)
}
func (o *organizationModule) GetAll(ctx context.Context) ([]*types.Organization, error) {
return o.store.GetAll(ctx)
}
func (o *organizationModule) Update(ctx context.Context, updatedOrganization *types.Organization) error {
return o.store.Update(ctx, updatedOrganization)
}

View File

@ -0,0 +1,102 @@
package implorganization
import (
"context"
"database/sql"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
type store struct {
store sqlstore.SQLStore
}
func NewStore(db sqlstore.SQLStore) types.OrganizationStore {
return &store{store: db}
}
func (s *store) Create(ctx context.Context, organization *types.Organization) error {
_, err := s.
store.
BunDB().
NewInsert().
Model(organization).
Exec(ctx)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to create organization")
}
return nil
}
func (s *store) Get(ctx context.Context, id valuer.UUID) (*types.Organization, error) {
organization := new(types.Organization)
err := s.
store.
BunDB().
NewSelect().
Model(organization).
Where("id = ?", id.StringValue()).
Scan(ctx)
if err != nil {
if err == sql.ErrNoRows {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "no organization found with id: %s", id.StringValue())
}
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to get organization with id: %s", id.StringValue())
}
return organization, nil
}
func (s *store) GetAll(ctx context.Context) ([]*types.Organization, error) {
organizations := make([]*types.Organization, 0)
err := s.
store.
BunDB().
NewSelect().
Model(&organizations).
Scan(ctx)
if err != nil {
if err == sql.ErrNoRows {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "no organizations found")
}
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to get all organizations")
}
return organizations, nil
}
func (s *store) Update(ctx context.Context, organization *types.Organization) error {
_, err := s.
store.
BunDB().
NewUpdate().
Model(organization).
Set("display_name = ?", organization.DisplayName).
Set("updated_at = ?", time.Now()).
Where("id = ?", organization.ID.StringValue()).
Exec(ctx)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to update organization with id: %s", organization.ID.StringValue())
}
return nil
}
func (s *store) Delete(ctx context.Context, id valuer.UUID) error {
_, err := s.
store.
BunDB().
NewDelete().
Model(new(types.Organization)).
Where("id = ?", id.StringValue()).
Exec(ctx)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete organization with id: %s", id.StringValue())
}
return nil
}

View File

@ -0,0 +1,34 @@
package organization
import (
"context"
"net/http"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Module interface {
// Create creates the given organization
Create(context.Context, *types.Organization) error
// Get gets the organization based on the given id
Get(context.Context, valuer.UUID) (*types.Organization, error)
// GetAll gets all the organizations
GetAll(context.Context) ([]*types.Organization, error)
// Update updates the given organization based on id present
Update(context.Context, *types.Organization) error
}
type API interface {
// Get gets the organization based on the id in claims
Get(http.ResponseWriter, *http.Request)
// GetAll gets all the organizations
GetAll(http.ResponseWriter, *http.Request)
// Update updates the organization based on the id in claims
Update(http.ResponseWriter, *http.Request)
}

View File

@ -15,7 +15,6 @@ type API interface {
GetOrgPreference(http.ResponseWriter, *http.Request) GetOrgPreference(http.ResponseWriter, *http.Request)
UpdateOrgPreference(http.ResponseWriter, *http.Request) UpdateOrgPreference(http.ResponseWriter, *http.Request)
GetAllOrgPreferences(http.ResponseWriter, *http.Request) GetAllOrgPreferences(http.ResponseWriter, *http.Request)
GetUserPreference(http.ResponseWriter, *http.Request) GetUserPreference(http.ResponseWriter, *http.Request)
UpdateUserPreference(http.ResponseWriter, *http.Request) UpdateUserPreference(http.ResponseWriter, *http.Request)
GetAllUserPreferences(http.ResponseWriter, *http.Request) GetAllUserPreferences(http.ResponseWriter, *http.Request)

View File

@ -10,7 +10,6 @@ type Usecase interface {
GetOrgPreference(ctx context.Context, preferenceId string, orgId string) (*preferencetypes.GettablePreference, error) GetOrgPreference(ctx context.Context, preferenceId string, orgId string) (*preferencetypes.GettablePreference, error)
UpdateOrgPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, orgId string) error UpdateOrgPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, orgId string) error
GetAllOrgPreferences(ctx context.Context, orgId string) ([]*preferencetypes.PreferenceWithValue, error) GetAllOrgPreferences(ctx context.Context, orgId string) ([]*preferencetypes.PreferenceWithValue, error)
GetUserPreference(ctx context.Context, preferenceId string, orgId string, userId string) (*preferencetypes.GettablePreference, error) GetUserPreference(ctx context.Context, preferenceId string, orgId string, userId string) (*preferencetypes.GettablePreference, error)
UpdateUserPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, userId string) error UpdateUserPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, userId string) error
GetAllUserPreferences(ctx context.Context, orgId string, userId string) ([]*preferencetypes.PreferenceWithValue, error) GetAllUserPreferences(ctx context.Context, orgId string, userId string) ([]*preferencetypes.PreferenceWithValue, error)

View File

@ -4,6 +4,8 @@ import (
"context" "context"
"testing" "testing"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/query-service/auth" "github.com/SigNoz/signoz/pkg/query-service/auth"
"github.com/SigNoz/signoz/pkg/query-service/constants" "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/dao" "github.com/SigNoz/signoz/pkg/query-service/dao"
@ -20,7 +22,8 @@ func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) {
controller, err := NewController(sqlStore) controller, err := NewController(sqlStore)
require.NoError(err) require.NoError(err)
user, apiErr := createTestUser() organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
user, apiErr := createTestUser(organizationModule)
require.Nil(apiErr) require.Nil(apiErr)
// should be able to generate connection url for // should be able to generate connection url for
@ -66,8 +69,8 @@ func TestAgentCheckIns(t *testing.T) {
sqlStore := utils.NewQueryServiceDBForTests(t) sqlStore := utils.NewQueryServiceDBForTests(t)
controller, err := NewController(sqlStore) controller, err := NewController(sqlStore)
require.NoError(err) require.NoError(err)
organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
user, apiErr := createTestUser() user, apiErr := createTestUser(organizationModule)
require.Nil(apiErr) require.Nil(apiErr)
// An agent should be able to check in from a cloud account even // An agent should be able to check in from a cloud account even
@ -118,7 +121,7 @@ func TestAgentCheckIns(t *testing.T) {
// After disconnecting existing account record, the agent should be able to // After disconnecting existing account record, the agent should be able to
// connected for a particular cloud account id // connected for a particular cloud account id
_, apiErr = controller.DisconnectAccount( _, _ = controller.DisconnectAccount(
context.TODO(), user.OrgID, "aws", testAccountId1, context.TODO(), user.OrgID, "aws", testAccountId1,
) )
@ -153,7 +156,8 @@ func TestCantDisconnectNonExistentAccount(t *testing.T) {
controller, err := NewController(sqlStore) controller, err := NewController(sqlStore)
require.NoError(err) require.NoError(err)
user, apiErr := createTestUser() organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
user, apiErr := createTestUser(organizationModule)
require.Nil(apiErr) require.Nil(apiErr)
// Attempting to disconnect a non-existent account should return error // Attempting to disconnect a non-existent account should return error
@ -171,7 +175,8 @@ func TestConfigureService(t *testing.T) {
controller, err := NewController(sqlStore) controller, err := NewController(sqlStore)
require.NoError(err) require.NoError(err)
user, apiErr := createTestUser() organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
user, apiErr := createTestUser(organizationModule)
require.Nil(apiErr) require.Nil(apiErr)
// create a connected account // create a connected account
@ -286,19 +291,18 @@ func makeTestConnectedAccount(t *testing.T, orgId string, controller *Controller
return acc return acc
} }
func createTestUser() (*types.User, *model.ApiError) { func createTestUser(organizationModule organization.Module) (*types.User, *model.ApiError) {
// Create a test user for auth // Create a test user for auth
ctx := context.Background() ctx := context.Background()
org, apiErr := dao.DB().CreateOrg(ctx, &types.Organization{ organization := types.NewOrganization("test")
Name: "test", err := organizationModule.Create(ctx, organization)
}) if err != nil {
if apiErr != nil { return nil, model.InternalError(err)
return nil, apiErr
} }
group, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup) group, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup)
if apiErr != nil { if apiErr != nil {
return nil, apiErr return nil, model.InternalError(apiErr)
} }
auth.InitAuthCache(ctx) auth.InitAuthCache(ctx)
@ -311,7 +315,7 @@ func createTestUser() (*types.User, *model.ApiError) {
Name: "test", Name: "test",
Email: userId[:8] + "test@test.com", Email: userId[:8] + "test@test.com",
Password: "test", Password: "test",
OrgID: org.ID, OrgID: organization.ID.StringValue(),
GroupID: group.ID, GroupID: group.ID,
}, },
true, true,

View File

@ -22,6 +22,7 @@ import (
"github.com/SigNoz/signoz/pkg/apis/fields" "github.com/SigNoz/signoz/pkg/apis/fields"
errorsV2 "github.com/SigNoz/signoz/pkg/errors" errorsV2 "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render" "github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/preference" "github.com/SigNoz/signoz/pkg/modules/preference"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations" "github.com/SigNoz/signoz/pkg/query-service/app/integrations"
"github.com/SigNoz/signoz/pkg/query-service/app/metricsexplorer" "github.com/SigNoz/signoz/pkg/query-service/app/metricsexplorer"
@ -148,6 +149,9 @@ type APIHandler struct {
Signoz *signoz.SigNoz Signoz *signoz.SigNoz
Preference preference.API Preference preference.API
OrganizationAPI organization.API
OrganizationModule organization.Module
} }
type APIHandlerOpts struct { type APIHandlerOpts struct {
@ -196,7 +200,9 @@ type APIHandlerOpts struct {
Signoz *signoz.SigNoz Signoz *signoz.SigNoz
Preference preference.API Preference preference.API
OrganizationAPI organization.API
OrganizationModule organization.Module
} }
// NewAPIHandler returns an APIHandler // NewAPIHandler returns an APIHandler
@ -267,6 +273,8 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
Signoz: opts.Signoz, Signoz: opts.Signoz,
Preference: opts.Preference, Preference: opts.Preference,
FieldsAPI: opts.FieldsAPI, FieldsAPI: opts.FieldsAPI,
OrganizationAPI: opts.OrganizationAPI,
OrganizationModule: opts.OrganizationModule,
} }
logsQueryBuilder := logsv3.PrepareLogsQuery logsQueryBuilder := logsv3.PrepareLogsQuery
@ -623,11 +631,12 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *AuthMiddleware) {
router.HandleFunc("/api/v1/rbac/role/{id}", am.SelfAccess(aH.getRole)).Methods(http.MethodGet) router.HandleFunc("/api/v1/rbac/role/{id}", am.SelfAccess(aH.getRole)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/rbac/role/{id}", am.AdminAccess(aH.editRole)).Methods(http.MethodPut) router.HandleFunc("/api/v1/rbac/role/{id}", am.AdminAccess(aH.editRole)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/org", am.AdminAccess(aH.getOrgs)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/org/{id}", am.AdminAccess(aH.getOrg)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/org/{id}", am.AdminAccess(aH.editOrg)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/orgUsers/{id}", am.AdminAccess(aH.getOrgUsers)).Methods(http.MethodGet) router.HandleFunc("/api/v1/orgUsers/{id}", am.AdminAccess(aH.getOrgUsers)).Methods(http.MethodGet)
router.HandleFunc("/api/v2/orgs", am.AdminAccess(aH.getOrgs)).Methods(http.MethodGet)
router.HandleFunc("/api/v2/orgs/me", am.AdminAccess(aH.getOrg)).Methods(http.MethodGet)
router.HandleFunc("/api/v2/orgs/me", am.AdminAccess(aH.updateOrg)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/getResetPasswordToken/{id}", am.AdminAccess(aH.getResetPasswordToken)).Methods(http.MethodGet) router.HandleFunc("/api/v1/getResetPasswordToken/{id}", am.AdminAccess(aH.getResetPasswordToken)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/resetPassword", am.OpenAccess(aH.resetPassword)).Methods(http.MethodPost) router.HandleFunc("/api/v1/resetPassword", am.OpenAccess(aH.resetPassword)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/changePassword/{id}", am.SelfAccess(aH.changePassword)).Methods(http.MethodPost) router.HandleFunc("/api/v1/changePassword/{id}", am.SelfAccess(aH.changePassword)).Methods(http.MethodPost)
@ -2058,7 +2067,7 @@ func (aH *APIHandler) inviteUsers(w http.ResponseWriter, r *http.Request) {
func (aH *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) {
token := mux.Vars(r)["token"] token := mux.Vars(r)["token"]
resp, err := auth.GetInvite(context.Background(), token) resp, err := auth.GetInvite(context.Background(), token, aH.OrganizationModule)
if err != nil { if err != nil {
RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorNotFound}, nil) RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorNotFound}, nil)
return return
@ -2096,10 +2105,13 @@ func (aH *APIHandler) listPendingInvites(w http.ResponseWriter, r *http.Request)
// we should include org name field in the invite table, or do a join query. // we should include org name field in the invite table, or do a join query.
var resp []*model.InvitationResponseObject var resp []*model.InvitationResponseObject
for _, inv := range invites { for _, inv := range invites {
orgID, err := valuer.NewUUID(inv.OrgID)
org, apiErr := dao.DB().GetOrg(ctx, inv.OrgID) if err != nil {
if apiErr != nil { render.Error(w, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, "invalid org_id in the invite"))
RespondError(w, apiErr, nil) }
org, err := aH.OrganizationModule.Get(ctx, orgID)
if err != nil {
render.Error(w, errorsV2.Newf(errorsV2.TypeInternal, errorsV2.CodeInternal, err.Error()))
} }
resp = append(resp, &model.InvitationResponseObject{ resp = append(resp, &model.InvitationResponseObject{
Name: inv.Name, Name: inv.Name,
@ -2124,7 +2136,7 @@ func (aH *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
return return
} }
_, apiErr := auth.Register(context.Background(), req, aH.Signoz.Alertmanager) _, apiErr := auth.Register(context.Background(), req, aH.Signoz.Alertmanager, aH.OrganizationModule)
if apiErr != nil { if apiErr != nil {
RespondError(w, apiErr, nil) RespondError(w, apiErr, nil)
return return
@ -2398,49 +2410,15 @@ func (aH *APIHandler) editRole(w http.ResponseWriter, r *http.Request) {
} }
func (aH *APIHandler) getOrgs(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getOrgs(w http.ResponseWriter, r *http.Request) {
orgs, apiErr := dao.DB().GetOrgs(context.Background()) aH.OrganizationAPI.GetAll(w, r)
if apiErr != nil {
RespondError(w, apiErr, "Failed to fetch orgs from the DB")
return
}
aH.WriteJSON(w, r, orgs)
} }
func (aH *APIHandler) getOrg(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getOrg(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] aH.OrganizationAPI.Get(w, r)
org, apiErr := dao.DB().GetOrg(context.Background(), id)
if apiErr != nil {
RespondError(w, apiErr, "Failed to fetch org from the DB")
return
}
aH.WriteJSON(w, r, org)
} }
func (aH *APIHandler) editOrg(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) updateOrg(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] aH.OrganizationAPI.Update(w, r)
req, err := parseEditOrgRequest(r)
if aH.HandleError(w, err, http.StatusBadRequest) {
return
}
req.ID = id
if apiErr := dao.DB().EditOrg(context.Background(), req); apiErr != nil {
RespondError(w, apiErr, "Failed to update org in the DB")
return
}
data := map[string]interface{}{
"hasOptedUpdates": req.HasOptedUpdates,
"isAnonymous": req.IsAnonymous,
"organizationName": req.Name,
}
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
zap.L().Error("failed to get user email from jwt")
}
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_ORG_SETTINGS, data, claims.Email, true, false)
aH.WriteJSON(w, r, map[string]string{"data": "org updated successfully"})
} }
func (aH *APIHandler) getOrgUsers(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getOrgUsers(w http.ResponseWriter, r *http.Request) {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"testing" "testing"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -11,10 +12,11 @@ import (
func TestIntegrationLifecycle(t *testing.T) { func TestIntegrationLifecycle(t *testing.T) {
require := require.New(t) require := require.New(t)
mgr := NewTestIntegrationsManager(t) mgr, store := NewTestIntegrationsManager(t)
ctx := context.Background() ctx := context.Background()
user, apiErr := createTestUser() organizationModule := implorganization.NewModule(implorganization.NewStore(store))
user, apiErr := createTestUser(organizationModule)
if apiErr != nil { if apiErr != nil {
t.Fatalf("could not create test user: %v", apiErr) t.Fatalf("could not create test user: %v", apiErr)
} }

View File

@ -5,19 +5,21 @@ import (
"slices" "slices"
"testing" "testing"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/query-service/auth" "github.com/SigNoz/signoz/pkg/query-service/auth"
"github.com/SigNoz/signoz/pkg/query-service/constants" "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/dao" "github.com/SigNoz/signoz/pkg/query-service/dao"
"github.com/SigNoz/signoz/pkg/query-service/model" "github.com/SigNoz/signoz/pkg/query-service/model"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3" v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/SigNoz/signoz/pkg/query-service/utils" "github.com/SigNoz/signoz/pkg/query-service/utils"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/pipelinetypes" "github.com/SigNoz/signoz/pkg/types/pipelinetypes"
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes" ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/google/uuid" "github.com/google/uuid"
) )
func NewTestIntegrationsManager(t *testing.T) *Manager { func NewTestIntegrationsManager(t *testing.T) (*Manager, sqlstore.SQLStore) {
testDB := utils.NewQueryServiceDBForTests(t) testDB := utils.NewQueryServiceDBForTests(t)
installedIntegrationsRepo, err := NewInstalledIntegrationsSqliteRepo(testDB) installedIntegrationsRepo, err := NewInstalledIntegrationsSqliteRepo(testDB)
@ -28,22 +30,21 @@ func NewTestIntegrationsManager(t *testing.T) *Manager {
return &Manager{ return &Manager{
availableIntegrationsRepo: &TestAvailableIntegrationsRepo{}, availableIntegrationsRepo: &TestAvailableIntegrationsRepo{},
installedIntegrationsRepo: installedIntegrationsRepo, installedIntegrationsRepo: installedIntegrationsRepo,
} }, testDB
} }
func createTestUser() (*types.User, *model.ApiError) { func createTestUser(organizationModule organization.Module) (*types.User, *model.ApiError) {
// Create a test user for auth // Create a test user for auth
ctx := context.Background() ctx := context.Background()
org, apiErr := dao.DB().CreateOrg(ctx, &types.Organization{ organization := types.NewOrganization("test")
Name: "test", err := organizationModule.Create(ctx, organization)
}) if err != nil {
if apiErr != nil { return nil, model.InternalError(err)
return nil, apiErr
} }
group, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup) group, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup)
if apiErr != nil { if apiErr != nil {
return nil, apiErr return nil, model.InternalError(apiErr)
} }
auth.InitAuthCache(ctx) auth.InitAuthCache(ctx)
@ -56,7 +57,7 @@ func createTestUser() (*types.User, *model.ApiError) {
Name: "test", Name: "test",
Email: userId[:8] + "test@test.com", Email: userId[:8] + "test@test.com",
Password: "test", Password: "test",
OrgID: org.ID, OrgID: organization.ID.StringValue(),
GroupID: group.ID, GroupID: group.ID,
}, },
true, true,

View File

@ -142,7 +142,7 @@ func (r *Repo) GetDefaultOrgID(ctx context.Context) (string, *model.ApiError) {
if len(orgs) == 0 { if len(orgs) == 0 {
return "", model.InternalError(errors.New("no orgs found")) return "", model.InternalError(errors.New("no orgs found"))
} }
return orgs[0].ID, nil return orgs[0].ID.StringValue(), nil
} }
// GetPipelines returns pipeline and errors (if any) // GetPipelines returns pipeline and errors (if any)

View File

@ -536,14 +536,6 @@ func parseSetApdexScoreRequest(r *http.Request) (*types.ApdexSettings, error) {
return &req, nil return &req, nil
} }
func parseInsertIngestionKeyRequest(r *http.Request) (*model.IngestionKey, error) {
var req model.IngestionKey
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
func parseRegisterRequest(r *http.Request) (*auth.RegisterRequest, error) { func parseRegisterRequest(r *http.Request) (*auth.RegisterRequest, error) {
var req auth.RegisterRequest var req auth.RegisterRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {

View File

@ -15,6 +15,7 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/apis/fields" "github.com/SigNoz/signoz/pkg/apis/fields"
"github.com/SigNoz/signoz/pkg/http/middleware" "github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/preference" "github.com/SigNoz/signoz/pkg/modules/preference"
preferencecore "github.com/SigNoz/signoz/pkg/modules/preference/core" preferencecore "github.com/SigNoz/signoz/pkg/modules/preference/core"
"github.com/SigNoz/signoz/pkg/prometheus" "github.com/SigNoz/signoz/pkg/prometheus"
@ -185,7 +186,9 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
} }
telemetry.GetInstance().SetReader(reader) telemetry.GetInstance().SetReader(reader)
preferenceModule := preference.NewAPI(preferencecore.NewPreference(preferencecore.NewStore(serverOptions.SigNoz.SQLStore), preferencetypes.NewDefaultPreferenceMap())) preferenceAPI := preference.NewAPI(preferencecore.NewPreference(preferencecore.NewStore(serverOptions.SigNoz.SQLStore), preferencetypes.NewDefaultPreferenceMap()))
organizationAPI := implorganization.NewAPI(implorganization.NewModule(implorganization.NewStore(serverOptions.SigNoz.SQLStore)))
organizationModule := implorganization.NewModule(implorganization.NewStore(serverOptions.SigNoz.SQLStore))
apiHandler, err := NewAPIHandler(APIHandlerOpts{ apiHandler, err := NewAPIHandler(APIHandlerOpts{
Reader: reader, Reader: reader,
SkipConfig: skipConfig, SkipConfig: skipConfig,
@ -204,7 +207,9 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
AlertmanagerAPI: alertmanager.NewAPI(serverOptions.SigNoz.Alertmanager), AlertmanagerAPI: alertmanager.NewAPI(serverOptions.SigNoz.Alertmanager),
FieldsAPI: fields.NewAPI(serverOptions.SigNoz.TelemetryStore), FieldsAPI: fields.NewAPI(serverOptions.SigNoz.TelemetryStore),
Signoz: serverOptions.SigNoz, Signoz: serverOptions.SigNoz,
Preference: preferenceModule, Preference: preferenceAPI,
OrganizationAPI: organizationAPI,
OrganizationModule: organizationModule,
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -12,6 +12,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/query-service/constants" "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/dao" "github.com/SigNoz/signoz/pkg/query-service/dao"
"github.com/SigNoz/signoz/pkg/query-service/model" "github.com/SigNoz/signoz/pkg/query-service/model"
@ -277,7 +278,7 @@ func RevokeInvite(ctx context.Context, email string) error {
} }
// GetInvite returns an invitation object for the given token. // GetInvite returns an invitation object for the given token.
func GetInvite(ctx context.Context, token string) (*model.InvitationResponseObject, error) { func GetInvite(ctx context.Context, token string, organizationModule organization.Module) (*model.InvitationResponseObject, error) {
zap.L().Debug("GetInvite method invoked for token", zap.String("token", token)) zap.L().Debug("GetInvite method invoked for token", zap.String("token", token))
inv, apiErr := dao.DB().GetInviteFromToken(ctx, token) inv, apiErr := dao.DB().GetInviteFromToken(ctx, token)
@ -289,11 +290,13 @@ func GetInvite(ctx context.Context, token string) (*model.InvitationResponseObje
return nil, errors.New("user is not invited") return nil, errors.New("user is not invited")
} }
// TODO(Ahsan): This is not the best way to add org name in the invite response. We should orgID, err := valuer.NewUUID(inv.OrgID)
// either include org name in the invite table or do a join query. if err != nil {
org, apiErr := dao.DB().GetOrg(ctx, inv.OrgID) return nil, err
if apiErr != nil { }
return nil, errors.Wrap(apiErr.Err, "failed to query the DB") org, err := organizationModule.Get(ctx, orgID)
if err != nil {
return nil, errors.Wrap(err, "failed to query the DB")
} }
return &model.InvitationResponseObject{ return &model.InvitationResponseObject{
Name: inv.Name, Name: inv.Name,
@ -301,7 +304,7 @@ func GetInvite(ctx context.Context, token string) (*model.InvitationResponseObje
Token: inv.Token, Token: inv.Token,
CreatedAt: inv.CreatedAt.Unix(), CreatedAt: inv.CreatedAt.Unix(),
Role: inv.Role, Role: inv.Role,
Organization: org.Name, Organization: org.DisplayName,
}, nil }, nil
} }
@ -390,21 +393,19 @@ func ChangePassword(ctx context.Context, req *model.ChangePasswordRequest) *mode
} }
type RegisterRequest struct { type RegisterRequest struct {
Name string `json:"name"` Name string `json:"name"`
OrgID string `json:"orgId"` OrgID string `json:"orgId"`
OrgName string `json:"orgName"` OrgDisplayName string `json:"orgDisplayName"`
Email string `json:"email"` OrgName string `json:"orgName"`
Password string `json:"password"` Email string `json:"email"`
InviteToken string `json:"token"` Password string `json:"password"`
IsAnonymous bool `json:"isAnonymous"` InviteToken string `json:"token"`
HasOptedUpdates bool `json:"hasOptedUpdates"`
// reference URL to track where the register request is coming from // reference URL to track where the register request is coming from
SourceUrl string `json:"sourceUrl"` SourceUrl string `json:"sourceUrl"`
} }
func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*types.User, *model.ApiError) { func RegisterFirstUser(ctx context.Context, req *RegisterRequest, organizationModule organization.Module) (*types.User, *model.ApiError) {
if req.Email == "" { if req.Email == "" {
return nil, model.BadRequest(model.ErrEmailRequired{}) return nil, model.BadRequest(model.ErrEmailRequired{})
} }
@ -414,13 +415,10 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*types.User,
} }
groupName := constants.AdminGroup groupName := constants.AdminGroup
organization := types.NewOrganization(req.OrgDisplayName)
// modify this to use bun err := organizationModule.Create(ctx, organization)
org, apierr := dao.DB().CreateOrg(ctx, if err != nil {
&types.Organization{Name: req.OrgName, IsAnonymous: req.IsAnonymous, HasOptedUpdates: req.HasOptedUpdates}) return nil, model.InternalError(err)
if apierr != nil {
zap.L().Error("CreateOrg failed", zap.Error(apierr.ToError()))
return nil, apierr
} }
group, apiErr := dao.DB().GetGroupByName(ctx, groupName) group, apiErr := dao.DB().GetGroupByName(ctx, groupName)
@ -430,8 +428,6 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*types.User,
} }
var hash string var hash string
var err error
hash, err = PasswordHash(req.Password) hash, err = PasswordHash(req.Password)
if err != nil { if err != nil {
zap.L().Error("failed to generate password hash when registering a user", zap.Error(err)) zap.L().Error("failed to generate password hash when registering a user", zap.Error(err))
@ -448,7 +444,7 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*types.User,
}, },
ProfilePictureURL: "", // Currently unused ProfilePictureURL: "", // Currently unused
GroupID: group.ID, GroupID: group.ID,
OrgID: org.ID, OrgID: organization.ID.StringValue(),
} }
return dao.DB().CreateUser(ctx, user, true) return dao.DB().CreateUser(ctx, user, true)
@ -553,7 +549,7 @@ func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword b
// Register registers a new user. For the first register request, it doesn't need an invite token // Register registers a new user. For the first register request, it doesn't need an invite token
// and also the first registration is an enforced ADMIN registration. Every subsequent request will // and also the first registration is an enforced ADMIN registration. Every subsequent request will
// need an invite token to go through. // need an invite token to go through.
func Register(ctx context.Context, req *RegisterRequest, alertmanager alertmanager.Alertmanager) (*types.User, *model.ApiError) { func Register(ctx context.Context, req *RegisterRequest, alertmanager alertmanager.Alertmanager, organizationModule organization.Module) (*types.User, *model.ApiError) {
users, err := dao.DB().GetUsers(ctx) users, err := dao.DB().GetUsers(ctx)
if err != nil { if err != nil {
return nil, model.InternalError(fmt.Errorf("failed to get user count")) return nil, model.InternalError(fmt.Errorf("failed to get user count"))
@ -561,7 +557,7 @@ func Register(ctx context.Context, req *RegisterRequest, alertmanager alertmanag
switch len(users) { switch len(users) {
case 0: case 0:
user, err := RegisterFirstUser(ctx, req) user, err := RegisterFirstUser(ctx, req, organizationModule)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -26,10 +26,6 @@ type Queries interface {
GetGroupByName(ctx context.Context, name string) (*types.Group, *model.ApiError) GetGroupByName(ctx context.Context, name string) (*types.Group, *model.ApiError)
GetGroups(ctx context.Context) ([]types.Group, *model.ApiError) GetGroups(ctx context.Context) ([]types.Group, *model.ApiError)
GetOrgs(ctx context.Context) ([]types.Organization, *model.ApiError)
GetOrgByName(ctx context.Context, name string) (*types.Organization, *model.ApiError)
GetOrg(ctx context.Context, id string) (*types.Organization, *model.ApiError)
GetResetPasswordEntry(ctx context.Context, token string) (*types.ResetPasswordRequest, *model.ApiError) GetResetPasswordEntry(ctx context.Context, token string) (*types.ResetPasswordRequest, *model.ApiError)
GetUsersByOrg(ctx context.Context, orgId string) ([]types.GettableUser, *model.ApiError) GetUsersByOrg(ctx context.Context, orgId string) ([]types.GettableUser, *model.ApiError)
GetUsersByGroup(ctx context.Context, groupId string) ([]types.GettableUser, *model.ApiError) GetUsersByGroup(ctx context.Context, groupId string) ([]types.GettableUser, *model.ApiError)
@ -50,10 +46,6 @@ type Mutations interface {
CreateGroup(ctx context.Context, group *types.Group) (*types.Group, *model.ApiError) CreateGroup(ctx context.Context, group *types.Group) (*types.Group, *model.ApiError)
DeleteGroup(ctx context.Context, id string) *model.ApiError DeleteGroup(ctx context.Context, id string) *model.ApiError
CreateOrg(ctx context.Context, org *types.Organization) (*types.Organization, *model.ApiError)
EditOrg(ctx context.Context, org *types.Organization) *model.ApiError
DeleteOrg(ctx context.Context, id string) *model.ApiError
CreateResetPasswordEntry(ctx context.Context, req *types.ResetPasswordRequest) *model.ApiError CreateResetPasswordEntry(ctx context.Context, req *types.ResetPasswordRequest) *model.ApiError
DeleteResetPasswordEntry(ctx context.Context, token string) *model.ApiError DeleteResetPasswordEntry(ctx context.Context, token string) *model.ApiError

View File

@ -65,7 +65,7 @@ func (mds *ModelDaoSqlite) initializeOrgPreferences(ctx context.Context) error {
} }
// set telemetry fields from userPreferences // set telemetry fields from userPreferences
telemetry.GetInstance().SetDistinctId(org.ID) telemetry.GetInstance().SetDistinctId(org.ID.StringValue())
users, _ := mds.GetUsers(ctx) users, _ := mds.GetUsers(ctx)
countUsers := len(users) countUsers := len(users)

View File

@ -3,7 +3,6 @@ package sqlite
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/SigNoz/signoz/pkg/query-service/model" "github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/query-service/telemetry" "github.com/SigNoz/signoz/pkg/query-service/telemetry"
@ -95,71 +94,6 @@ func (mds *ModelDaoSqlite) GetInvites(ctx context.Context, orgID string) ([]type
return invites, nil return invites, nil
} }
func (mds *ModelDaoSqlite) CreateOrg(ctx context.Context,
org *types.Organization) (*types.Organization, *model.ApiError) {
org.ID = uuid.NewString()
org.CreatedAt = time.Now()
_, err := mds.bundb.NewInsert().
Model(org).
Exec(ctx)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return org, nil
}
func (mds *ModelDaoSqlite) GetOrg(ctx context.Context,
id string) (*types.Organization, *model.ApiError) {
orgs := []types.Organization{}
if err := mds.bundb.NewSelect().
Model(&orgs).
Where("id = ?", id).
Scan(ctx); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
// TODO(nitya): remove for multitenancy
if len(orgs) > 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: errors.New("Found multiple org with same ID"),
}
}
if len(orgs) == 0 {
return nil, nil
}
return &orgs[0], nil
}
func (mds *ModelDaoSqlite) GetOrgByName(ctx context.Context,
name string) (*types.Organization, *model.ApiError) {
orgs := []types.Organization{}
if err := mds.bundb.NewSelect().
Model(&orgs).
Where("name = ?", name).
Scan(ctx); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(orgs) > 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: errors.New("Multiple orgs with same ID found"),
}
}
if len(orgs) == 0 {
return nil, nil
}
return &orgs[0], nil
}
func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]types.Organization, *model.ApiError) { func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]types.Organization, *model.ApiError) {
var orgs []types.Organization var orgs []types.Organization
err := mds.bundb.NewSelect(). err := mds.bundb.NewSelect().
@ -172,37 +106,6 @@ func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]types.Organization, *
return orgs, nil return orgs, nil
} }
func (mds *ModelDaoSqlite) EditOrg(ctx context.Context, org *types.Organization) *model.ApiError {
_, err := mds.bundb.NewUpdate().
Model(org).
Column("name").
Column("has_opted_updates").
Column("is_anonymous").
Where("id = ?", org.ID).
Exec(ctx)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
telemetry.GetInstance().SetTelemetryAnonymous(org.IsAnonymous)
telemetry.GetInstance().SetDistinctId(org.ID)
return nil
}
func (mds *ModelDaoSqlite) DeleteOrg(ctx context.Context, id string) *model.ApiError {
_, err := mds.bundb.NewDelete().
Model(&types.Organization{}).
Where("id = ?", id).
Exec(ctx)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (mds *ModelDaoSqlite) CreateUser(ctx context.Context, func (mds *ModelDaoSqlite) CreateUser(ctx context.Context,
user *types.User, isFirstUser bool) (*types.User, *model.ApiError) { user *types.User, isFirstUser bool) (*types.User, *model.ApiError) {
_, err := mds.bundb.NewInsert(). _, err := mds.bundb.NewInsert().
@ -306,7 +209,7 @@ func (mds *ModelDaoSqlite) GetUser(ctx context.Context,
Table("users"). Table("users").
Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id"). Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id").
ColumnExpr("g.name as role"). ColumnExpr("g.name as role").
ColumnExpr("o.name as organization"). ColumnExpr("o.display_name as organization").
Join("JOIN groups g ON g.id = users.group_id"). Join("JOIN groups g ON g.id = users.group_id").
Join("JOIN organizations o ON o.id = users.org_id"). Join("JOIN organizations o ON o.id = users.org_id").
Where("users.id = ?", id) Where("users.id = ?", id)
@ -343,7 +246,7 @@ func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
Table("users"). Table("users").
Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id"). Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id").
ColumnExpr("g.name as role"). ColumnExpr("g.name as role").
ColumnExpr("o.name as organization"). ColumnExpr("o.display_name as organization").
Join("JOIN groups g ON g.id = users.group_id"). Join("JOIN groups g ON g.id = users.group_id").
Join("JOIN organizations o ON o.id = users.org_id"). Join("JOIN organizations o ON o.id = users.org_id").
Where("users.email = ?", email) Where("users.email = ?", email)
@ -378,7 +281,7 @@ func (mds *ModelDaoSqlite) GetUsersWithOpts(ctx context.Context, limit int) ([]t
Table("users"). Table("users").
Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id"). Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id").
ColumnExpr("g.name as role"). ColumnExpr("g.name as role").
ColumnExpr("o.name as organization"). ColumnExpr("o.display_name as organization").
Join("JOIN groups g ON g.id = users.group_id"). Join("JOIN groups g ON g.id = users.group_id").
Join("JOIN organizations o ON o.id = users.org_id") Join("JOIN organizations o ON o.id = users.org_id")
@ -402,7 +305,7 @@ func (mds *ModelDaoSqlite) GetUsersByOrg(ctx context.Context,
Table("users"). Table("users").
Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id"). Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id").
ColumnExpr("g.name as role"). ColumnExpr("g.name as role").
ColumnExpr("o.name as organization"). ColumnExpr("o.display_name as organization").
Join("JOIN groups g ON g.id = users.group_id"). Join("JOIN groups g ON g.id = users.group_id").
Join("JOIN organizations o ON o.id = users.org_id"). Join("JOIN organizations o ON o.id = users.org_id").
Where("users.org_id = ?", orgId) Where("users.org_id = ?", orgId)
@ -423,7 +326,7 @@ func (mds *ModelDaoSqlite) GetUsersByGroup(ctx context.Context,
Table("users"). Table("users").
Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id"). Column("users.id", "users.name", "users.email", "users.password", "users.created_at", "users.profile_picture_url", "users.org_id", "users.group_id").
ColumnExpr("g.name as role"). ColumnExpr("g.name as role").
ColumnExpr("o.name as organization"). ColumnExpr("o.display_name as organization").
Join("JOIN groups g ON g.id = users.group_id"). Join("JOIN groups g ON g.id = users.group_id").
Join("JOIN organizations o ON o.id = users.org_id"). Join("JOIN organizations o ON o.id = users.org_id").
Where("users.group_id = ?", groupId) Where("users.group_id = ?", groupId)

View File

@ -10,6 +10,7 @@ import (
"testing" "testing"
"github.com/SigNoz/signoz/pkg/http/middleware" "github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/query-service/app" "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/query-service/auth" "github.com/SigNoz/signoz/pkg/query-service/auth"
"github.com/SigNoz/signoz/pkg/query-service/constants" "github.com/SigNoz/signoz/pkg/query-service/constants"
@ -313,7 +314,8 @@ func NewFilterSuggestionsTestBed(t *testing.T) *FilterSuggestionsTestBed {
apiHandler.RegisterRoutes(router, am) apiHandler.RegisterRoutes(router, am)
apiHandler.RegisterQueryRangeV3Routes(router, am) apiHandler.RegisterQueryRangeV3Routes(router, am)
user, apiErr := createTestUser() organizationModule := implorganization.NewModule(implorganization.NewStore(testDB))
user, apiErr := createTestUser(organizationModule)
if apiErr != nil { if apiErr != nil {
t.Fatalf("could not create a test user: %v", apiErr) t.Fatalf("could not create a test user: %v", apiErr)
} }

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/query-service/agentConf" "github.com/SigNoz/signoz/pkg/query-service/agentConf"
"github.com/SigNoz/signoz/pkg/query-service/app" "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations" "github.com/SigNoz/signoz/pkg/query-service/app/integrations"
@ -479,7 +480,8 @@ func NewTestbedWithoutOpamp(t *testing.T, sqlStore sqlstore.SQLStore) *LogPipeli
t.Fatalf("could not create a new ApiHandler: %v", err) t.Fatalf("could not create a new ApiHandler: %v", err)
} }
user, apiErr := createTestUser() organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
user, apiErr := createTestUser(organizationModule)
if apiErr != nil { if apiErr != nil {
t.Fatalf("could not create a test user: %v", apiErr) t.Fatalf("could not create a test user: %v", apiErr)
} }

View File

@ -9,6 +9,8 @@ import (
"time" "time"
"github.com/SigNoz/signoz/pkg/http/middleware" "github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/query-service/app" "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations" "github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
"github.com/SigNoz/signoz/pkg/query-service/auth" "github.com/SigNoz/signoz/pkg/query-service/auth"
@ -375,7 +377,8 @@ func NewCloudIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *CloudI
apiHandler.RegisterRoutes(router, am) apiHandler.RegisterRoutes(router, am)
apiHandler.RegisterCloudIntegrationsRoutes(router, am) apiHandler.RegisterCloudIntegrationsRoutes(router, am)
user, apiErr := createTestUser() organizationModule := implorganization.NewModule(implorganization.NewStore(testDB))
user, apiErr := createTestUser(organizationModule)
if apiErr != nil { if apiErr != nil {
t.Fatalf("could not create a test user: %v", apiErr) t.Fatalf("could not create a test user: %v", apiErr)
} }

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/SigNoz/signoz/pkg/http/middleware" "github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/query-service/app" "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations" "github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations" "github.com/SigNoz/signoz/pkg/query-service/app/integrations"
@ -583,7 +584,8 @@ func NewIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *Integration
apiHandler.RegisterRoutes(router, am) apiHandler.RegisterRoutes(router, am)
apiHandler.RegisterIntegrationRoutes(router, am) apiHandler.RegisterIntegrationRoutes(router, am)
user, apiErr := createTestUser() organizationModule := implorganization.NewModule(implorganization.NewStore(testDB))
user, apiErr := createTestUser(organizationModule)
if apiErr != nil { if apiErr != nil {
t.Fatalf("could not create a test user: %v", apiErr) t.Fatalf("could not create a test user: %v", apiErr)
} }

View File

@ -14,6 +14,7 @@ import (
"github.com/DATA-DOG/go-sqlmock" "github.com/DATA-DOG/go-sqlmock"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest" "github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/prometheus" "github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/prometheus/prometheustest" "github.com/SigNoz/signoz/pkg/prometheus/prometheustest"
"github.com/SigNoz/signoz/pkg/query-service/app" "github.com/SigNoz/signoz/pkg/query-service/app"
@ -148,19 +149,18 @@ func makeTestSignozLog(
return testLog return testLog
} }
func createTestUser() (*types.User, *model.ApiError) { func createTestUser(organizationModule organization.Module) (*types.User, *model.ApiError) {
// Create a test user for auth // Create a test user for auth
ctx := context.Background() ctx := context.Background()
org, apiErr := dao.DB().CreateOrg(ctx, &types.Organization{ organization := types.NewOrganization("test")
Name: "test", err := organizationModule.Create(ctx, organization)
}) if err != nil {
if apiErr != nil { return nil, model.InternalError(err)
return nil, apiErr
} }
group, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup) group, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup)
if apiErr != nil { if apiErr != nil {
return nil, apiErr return nil, model.InternalError(apiErr)
} }
auth.InitAuthCache(ctx) auth.InitAuthCache(ctx)
@ -174,7 +174,7 @@ func createTestUser() (*types.User, *model.ApiError) {
Name: "test", Name: "test",
Email: userId[:8] + "test@test.com", Email: userId[:8] + "test@test.com",
Password: "test", Password: "test",
OrgID: org.ID, OrgID: organization.ID.StringValue(),
GroupID: group.ID, GroupID: group.ID,
}, },
true, true,

View File

@ -57,6 +57,7 @@ func NewTestSqliteDB(t *testing.T) (sqlStore sqlstore.SQLStore, testDBFilePath s
sqlmigration.NewUpdatePatFactory(sqlStore), sqlmigration.NewUpdatePatFactory(sqlStore),
sqlmigration.NewAddVirtualFieldsFactory(), sqlmigration.NewAddVirtualFieldsFactory(),
sqlmigration.NewUpdateIntegrationsFactory(sqlStore), sqlmigration.NewUpdateIntegrationsFactory(sqlStore),
sqlmigration.NewUpdateOrganizationsFactory(sqlStore),
), ),
) )
if err != nil { if err != nil {

View File

@ -72,6 +72,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM
sqlmigration.NewUpdateRulesFactory(sqlstore), sqlmigration.NewUpdateRulesFactory(sqlstore),
sqlmigration.NewAddVirtualFieldsFactory(), sqlmigration.NewAddVirtualFieldsFactory(),
sqlmigration.NewUpdateIntegrationsFactory(sqlstore), sqlmigration.NewUpdateIntegrationsFactory(sqlstore),
sqlmigration.NewUpdateOrganizationsFactory(sqlstore),
) )
} }

View File

@ -0,0 +1,119 @@
package sqlmigration
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
)
type updateOrganizations struct {
store sqlstore.SQLStore
}
func NewUpdateOrganizationsFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(
factory.MustNewName("update_organizations"),
func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
return newUpdateOrganizations(ctx, ps, c, sqlstore)
})
}
func newUpdateOrganizations(_ context.Context, _ factory.ProviderSettings, _ Config, store sqlstore.SQLStore) (SQLMigration, error) {
return &updateOrganizations{store: store}, nil
}
func (migration *updateOrganizations) Register(migrations *migrate.Migrations) error {
if err := migrations.
Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *updateOrganizations) Up(ctx context.Context, db *bun.DB) error {
tx, err := db.
BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
err = migration.
store.
Dialect().
DropColumn(ctx, tx, "organizations", "is_anonymous")
if err != nil {
return err
}
err = migration.
store.
Dialect().
DropColumn(ctx, tx, "organizations", "has_opted_updates")
if err != nil {
return err
}
_, err = migration.
store.
Dialect().
RenameColumn(ctx, tx, "organizations", "name", "display_name")
if err != nil {
return err
}
err = migration.
store.
Dialect().
AddColumn(ctx, tx, "organizations", "name", "TEXT")
if err != nil {
return err
}
_, err = tx.
NewCreateIndex().
Unique().
IfNotExists().
Index("idx_unique_name").
Table("organizations").
Column("name").
Exec(ctx)
if err != nil {
return err
}
err = migration.
store.
Dialect().
AddColumn(ctx, tx, "organizations", "alias", "TEXT")
if err != nil {
return err
}
_, err = tx.
NewCreateIndex().
Unique().
IfNotExists().
Index("idx_unique_alias").
Table("organizations").
Column("alias").
Exec(ctx)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
return err
}
return nil
}
func (migration *updateOrganizations) Down(context.Context, *bun.DB) error {
return nil
}

View File

@ -74,6 +74,14 @@ func (dialect *dialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB,
} }
func (dialect *dialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error { func (dialect *dialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error {
columnExists, err := dialect.ColumnExists(ctx, bun, table, column)
if err != nil {
return err
}
if !columnExists {
return nil
}
columnType, err := dialect.GetColumnType(ctx, bun, table, column) columnType, err := dialect.GetColumnType(ctx, bun, table, column)
if err != nil { if err != nil {
return err return err
@ -141,6 +149,26 @@ func (dialect *dialect) ColumnExists(ctx context.Context, bun bun.IDB, table str
return count > 0, nil return count > 0, nil
} }
func (dialect *dialect) AddColumn(ctx context.Context, bun bun.IDB, table string, column string, columnExpr string) error {
exists, err := dialect.ColumnExists(ctx, bun, table, column)
if err != nil {
return err
}
if !exists {
_, err = bun.
NewAddColumn().
Table(table).
ColumnExpr(column + " " + columnExpr).
Exec(ctx)
if err != nil {
return err
}
}
return nil
}
func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table string, oldColumnName string, newColumnName string) (bool, error) { func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table string, oldColumnName string, newColumnName string) (bool, error) {
oldColumnExists, err := dialect.ColumnExists(ctx, bun, table, oldColumnName) oldColumnExists, err := dialect.ColumnExists(ctx, bun, table, oldColumnName)
if err != nil { if err != nil {
@ -152,10 +180,14 @@ func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table str
return false, err return false, err
} }
if !oldColumnExists && newColumnExists { if newColumnExists {
return true, nil return true, nil
} }
if !oldColumnExists {
return false, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "old column: %s doesn't exist", oldColumnName)
}
_, err = bun. _, err = bun.
ExecContext(ctx, "ALTER TABLE "+table+" RENAME COLUMN "+oldColumnName+" TO "+newColumnName) ExecContext(ctx, "ALTER TABLE "+table+" RENAME COLUMN "+oldColumnName+" TO "+newColumnName)
if err != nil { if err != nil {
@ -164,6 +196,26 @@ func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table str
return true, nil return true, nil
} }
func (dialect *dialect) DropColumn(ctx context.Context, bun bun.IDB, table string, column string) error {
exists, err := dialect.ColumnExists(ctx, bun, table, column)
if err != nil {
return err
}
if exists {
_, err = bun.
NewDropColumn().
Table(table).
Column(column).
Exec(ctx)
if err != nil {
return err
}
}
return nil
}
func (dialect *dialect) TableExists(ctx context.Context, bun bun.IDB, table interface{}) (bool, error) { func (dialect *dialect) TableExists(ctx context.Context, bun bun.IDB, table interface{}) (bool, error) {
count := 0 count := 0

View File

@ -42,7 +42,9 @@ type SQLDialect interface {
AddNotNullDefaultToColumn(context.Context, bun.IDB, string, string, string, string) error AddNotNullDefaultToColumn(context.Context, bun.IDB, string, string, string, string) error
GetColumnType(context.Context, bun.IDB, string, string) (string, error) GetColumnType(context.Context, bun.IDB, string, string) (string, error)
ColumnExists(context.Context, bun.IDB, string, string) (bool, error) ColumnExists(context.Context, bun.IDB, string, string) (bool, error)
AddColumn(context.Context, bun.IDB, string, string, string) error
RenameColumn(context.Context, bun.IDB, string, string, string) (bool, error) RenameColumn(context.Context, bun.IDB, string, string, string) (bool, error)
DropColumn(context.Context, bun.IDB, string, string) error
RenameTableAndModifyModel(context.Context, bun.IDB, interface{}, interface{}, []string, func(context.Context) error) error RenameTableAndModifyModel(context.Context, bun.IDB, interface{}, interface{}, []string, func(context.Context) error) error
UpdatePrimaryKey(context.Context, bun.IDB, interface{}, interface{}, string, func(context.Context) error) error UpdatePrimaryKey(context.Context, bun.IDB, interface{}, interface{}, string, func(context.Context) error) error
AddPrimaryKey(context.Context, bun.IDB, interface{}, interface{}, string, func(context.Context) error) error AddPrimaryKey(context.Context, bun.IDB, interface{}, interface{}, string, func(context.Context) error) error

View File

@ -25,10 +25,18 @@ func (dialect *dialect) ColumnExists(ctx context.Context, bun bun.IDB, table str
return false, nil return false, nil
} }
func (dialect *dialect) AddColumn(ctx context.Context, bun bun.IDB, table string, column string, columnExpr string) error {
return nil
}
func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table string, oldColumnName string, newColumnName string) (bool, error) { func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table string, oldColumnName string, newColumnName string) (bool, error) {
return true, nil return true, nil
} }
func (dialect *dialect) DropColumn(ctx context.Context, bun bun.IDB, table string, column string) error {
return nil
}
func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, references []string, cb func(context.Context) error) error { func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, references []string, cb func(context.Context) error) error {
return nil return nil
} }

View File

@ -1,17 +1,34 @@
package types package types
import ( import (
"context"
"time"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
// TODO: check constraints are not working
type Organization struct { type Organization struct {
bun.BaseModel `bun:"table:organizations"` bun.BaseModel `bun:"table:organizations"`
TimeAuditable TimeAuditable
ID string `bun:"id,pk,type:text" json:"id"` Identifiable
Name string `bun:"name,type:text,notnull" json:"name"` Name string `bun:"name,type:text,nullzero" json:"name"`
IsAnonymous bool `bun:"is_anonymous,notnull,default:0,CHECK(is_anonymous IN (0,1))" json:"isAnonymous"` Alias string `bun:"alias,type:text,nullzero" json:"alias"`
HasOptedUpdates bool `bun:"has_opted_updates,notnull,default:1,CHECK(has_opted_updates IN (0,1))" json:"hasOptedUpdates"` DisplayName string `bun:"display_name,type:text,notnull" json:"displayName"`
}
func NewOrganization(displayName string) *Organization {
return &Organization{
Identifiable: Identifiable{
ID: valuer.GenerateUUID(),
},
TimeAuditable: TimeAuditable{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
// Name: "default/main", TODO: take the call and uncomment this later
DisplayName: displayName,
}
} }
type ApdexSettings struct { type ApdexSettings struct {
@ -22,3 +39,11 @@ type ApdexSettings struct {
Threshold float64 `bun:"threshold,type:float,notnull" json:"threshold"` Threshold float64 `bun:"threshold,type:float,notnull" json:"threshold"`
ExcludeStatusCodes string `bun:"exclude_status_codes,type:text,notnull" json:"excludeStatusCodes"` ExcludeStatusCodes string `bun:"exclude_status_codes,type:text,notnull" json:"excludeStatusCodes"`
} }
type OrganizationStore interface {
Create(context.Context, *Organization) error
Get(context.Context, valuer.UUID) (*Organization, error)
GetAll(context.Context) ([]*Organization, error)
Update(context.Context, *Organization) error
Delete(context.Context, valuer.UUID) error
}