From c322657666d46a3603ddaacd9141c0a853dcc7d2 Mon Sep 17 00:00:00 2001 From: Vikrant Gupta Date: Fri, 25 Apr 2025 19:38:15 +0530 Subject: [PATCH] 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 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 * 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 --- ee/query-service/app/api/api.go | 5 + ee/query-service/app/api/auth.go | 7 +- .../integrations/signozio/signozio.go | 3 +- ee/query-service/usage/manager.go | 11 +- ee/sqlstore/postgressqlstore/dialect.go | 54 +++++++- frontend/src/AppRoutes/index.tsx | 2 +- frontend/src/api/user/editOrg.ts | 10 +- .../CustomDomainSettings.tsx | 2 +- .../OrgQuestions/OrgQuestions.tsx | 12 +- .../OnboardingQuestionaire/index.tsx | 2 +- .../AddDataSource/AddDataSource.tsx | 6 +- .../DisplayName/index.tsx | 26 ++-- .../container/OrganizationSettings/index.tsx | 7 +- frontend/src/pages/SignUp/SignUp.tsx | 58 ++------- frontend/src/providers/App/App.tsx | 12 +- frontend/src/tests/test-utils.tsx | 4 +- frontend/src/types/api/user/editOrg.ts | 4 +- .../src/types/api/user/getInviteDetails.ts | 2 +- .../src/types/api/user/getOrganization.ts | 4 +- frontend/src/types/api/user/signup.ts | 4 +- frontend/src/types/common/index.ts | 4 +- frontend/tests/fixtures/common.ts | 2 +- .../organization/implorganization/api.go | 80 ++++++++++++ .../organization/implorganization/module.go | 33 +++++ .../organization/implorganization/store.go | 102 +++++++++++++++ pkg/modules/organization/organization.go | 34 +++++ pkg/modules/preference/api.go | 1 - pkg/modules/preference/usecase.go | 1 - .../app/cloudintegrations/controller_test.go | 32 ++--- pkg/query-service/app/http_handler.go | 74 ++++------- .../app/integrations/manager_test.go | 6 +- .../app/integrations/test_utils.go | 21 ++-- .../app/logparsingpipeline/db.go | 2 +- pkg/query-service/app/parser.go | 8 -- pkg/query-service/app/server.go | 9 +- pkg/query-service/auth/auth.go | 54 ++++---- pkg/query-service/dao/interface.go | 8 -- pkg/query-service/dao/sqlite/connection.go | 2 +- pkg/query-service/dao/sqlite/rbac.go | 107 +--------------- .../integration/filter_suggestions_test.go | 4 +- .../integration/logparsingpipeline_test.go | 4 +- .../signoz_cloud_integrations_test.go | 5 +- .../integration/signoz_integrations_test.go | 4 +- .../tests/integration/test_utils.go | 16 +-- pkg/query-service/utils/testutils.go | 1 + pkg/signoz/provider.go | 1 + pkg/sqlmigration/028_update_organizations.go | 119 ++++++++++++++++++ pkg/sqlstore/sqlitesqlstore/dialect.go | 54 +++++++- pkg/sqlstore/sqlstore.go | 2 + pkg/sqlstore/sqlstoretest/dialect.go | 8 ++ pkg/types/organization.go | 35 +++++- 51 files changed, 690 insertions(+), 378 deletions(-) create mode 100644 pkg/modules/organization/implorganization/api.go create mode 100644 pkg/modules/organization/implorganization/module.go create mode 100644 pkg/modules/organization/implorganization/store.go create mode 100644 pkg/modules/organization/organization.go create mode 100644 pkg/sqlmigration/028_update_organizations.go diff --git a/ee/query-service/app/api/api.go b/ee/query-service/app/api/api.go index 327414d1f8..00e552d07b 100644 --- a/ee/query-service/app/api/api.go +++ b/ee/query-service/app/api/api.go @@ -12,6 +12,7 @@ import ( "github.com/SigNoz/signoz/ee/query-service/usage" "github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/apis/fields" + "github.com/SigNoz/signoz/pkg/modules/organization/implorganization" "github.com/SigNoz/signoz/pkg/modules/preference" preferencecore "github.com/SigNoz/signoz/pkg/modules/preference/core" baseapp "github.com/SigNoz/signoz/pkg/query-service/app" @@ -59,6 +60,8 @@ type APIHandler struct { // NewAPIHandler returns an APIHandler func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, error) { 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{ Reader: opts.DataConnector, @@ -78,6 +81,8 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, FieldsAPI: fields.NewAPI(signoz.TelemetryStore), Signoz: signoz, Preference: preference, + OrganizationAPI: organizationAPI, + OrganizationModule: organizationModule, }) if err != nil { diff --git a/ee/query-service/app/api/auth.go b/ee/query-service/app/api/auth.go index 03e3c7030a..469d39f85b 100644 --- a/ee/query-service/app/api/auth.go +++ b/ee/query-service/app/api/auth.go @@ -134,7 +134,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) { return } - _, registerError := baseauth.Register(ctx, req, ah.Signoz.Alertmanager) + _, registerError := baseauth.Register(ctx, req, ah.Signoz.Alertmanager, ah.OrganizationModule) if !registerError.IsNil() { RespondError(w, apierr, nil) 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) { token := mux.Vars(r)["token"] 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 { RespondError(w, model.BadRequest(err), nil) return @@ -163,7 +162,7 @@ func (ah *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) { InvitationResponseObject: inviteObject, } - precheck, apierr := ah.AppDao().PrecheckLogin(ctx, inviteObject.Email, sourceUrl) + precheck, apierr := ah.AppDao().PrecheckLogin(r.Context(), inviteObject.Email, sourceUrl) resp.Precheck = precheck if apierr != nil { diff --git a/ee/query-service/integrations/signozio/signozio.go b/ee/query-service/integrations/signozio/signozio.go index ff4f307441..54eaf3df99 100644 --- a/ee/query-service/integrations/signozio/signozio.go +++ b/ee/query-service/integrations/signozio/signozio.go @@ -9,10 +9,9 @@ import ( "net/http" "time" - "github.com/pkg/errors" - "github.com/SigNoz/signoz/ee/query-service/constants" "github.com/SigNoz/signoz/ee/query-service/model" + "github.com/pkg/errors" ) var C *Client diff --git a/ee/query-service/usage/manager.go b/ee/query-service/usage/manager.go index bcd154b21d..fbcc5304f9 100644 --- a/ee/query-service/usage/manager.go +++ b/ee/query-service/usage/manager.go @@ -138,15 +138,6 @@ func (lm *Manager) UploadUsage() { 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{} for _, usage := range usages { usageDataBytes, err := encryption.Decrypt([]byte(usage.ExporterID[:32]), []byte(usage.Data)) @@ -166,7 +157,7 @@ func (lm *Manager) UploadUsage() { usageData.ExporterID = usage.ExporterID usageData.Type = usage.Type usageData.Tenant = "default" - usageData.OrgName = orgName + usageData.OrgName = "default" usageData.TenantId = lm.tenantID usagesPayload = append(usagesPayload, usageData) } diff --git a/ee/sqlstore/postgressqlstore/dialect.go b/ee/sqlstore/postgressqlstore/dialect.go index e80469ae52..1a7ab7eb50 100644 --- a/ee/sqlstore/postgressqlstore/dialect.go +++ b/ee/sqlstore/postgressqlstore/dialect.go @@ -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 { + 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) if err != nil { return err @@ -151,6 +159,26 @@ func (dialect *dialect) ColumnExists(ctx context.Context, bun bun.IDB, table str 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) { oldColumnExists, err := dialect.ColumnExists(ctx, bun, table, oldColumnName) if err != nil { @@ -162,10 +190,14 @@ func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table str return false, err } - if !oldColumnExists && newColumnExists { + if newColumnExists { return true, nil } + if !oldColumnExists { + return false, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, fmt.Sprintf("old column: %s doesn't exist", oldColumnName)) + } + _, err = bun. ExecContext(ctx, "ALTER TABLE "+table+" RENAME COLUMN "+oldColumnName+" TO "+newColumnName) if err != nil { @@ -174,6 +206,26 @@ func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table str 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) { count := 0 diff --git a/frontend/src/AppRoutes/index.tsx b/frontend/src/AppRoutes/index.tsx index c588f28a45..73e633a673 100644 --- a/frontend/src/AppRoutes/index.tsx +++ b/frontend/src/AppRoutes/index.tsx @@ -64,7 +64,7 @@ function App(): JSX.Element { // wait for the required data to be loaded before doing init for anything! if (!isFetchingActiveLicenseV3 && activeLicenseV3 && org) { 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; diff --git a/frontend/src/api/user/editOrg.ts b/frontend/src/api/user/editOrg.ts index da980acea0..528991c61e 100644 --- a/frontend/src/api/user/editOrg.ts +++ b/frontend/src/api/user/editOrg.ts @@ -1,4 +1,4 @@ -import axios from 'api'; +import { ApiV2Instance as axios } from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError } from 'axios'; import { ErrorResponse, SuccessResponse } from 'types/api'; @@ -8,14 +8,12 @@ const editOrg = async ( props: Props, ): Promise | ErrorResponse> => { try { - const response = await axios.put(`/org/${props.orgId}`, { - name: props.name, - isAnonymous: props.isAnonymous, - hasOptedUpdates: props.hasOptedUpdates, + const response = await axios.put(`/orgs/me`, { + displayName: props.displayName, }); return { - statusCode: 200, + statusCode: 204, error: null, message: response.data.status, payload: response.data, diff --git a/frontend/src/container/CustomDomainSettings/CustomDomainSettings.tsx b/frontend/src/container/CustomDomainSettings/CustomDomainSettings.tsx index a4aec25c61..730d138323 100644 --- a/frontend/src/container/CustomDomainSettings/CustomDomainSettings.tsx +++ b/frontend/src/container/CustomDomainSettings/CustomDomainSettings.tsx @@ -160,7 +160,7 @@ export default function CustomDomainSettings(): JSX.Element { {!isLoadingDeploymentsData && (
- Team {org?.[0]?.name} Information + Team {org?.[0]?.displayName} Information
diff --git a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx index e7bd6fb58c..e3cbf3db36 100644 --- a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx @@ -13,8 +13,7 @@ import { useTranslation } from 'react-i18next'; export interface OrgData { id: string; - isAnonymous: boolean; - name: string; + displayName: string; } export interface OrgDetails { @@ -110,15 +109,14 @@ function OrgQuestions({ try { setIsLoading(true); const { statusCode, error } = await editOrg({ - isAnonymous: currentOrgData.isAnonymous, - name: organisationName, + displayName: organisationName, orgId: currentOrgData.id, }); - if (statusCode === 200) { - updateOrg(currentOrgData?.id, orgDetails.organisationName); + if (statusCode === 204) { + updateOrg(currentOrgData?.id, organisationName); logEvent('Org Onboarding: Org Name Updated', { - organisationName: orgDetails.organisationName, + organisationName, }); logEvent('Org Onboarding: Answered', { diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index e383a487dd..d57b4389a7 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -94,7 +94,7 @@ function OnboardingQuestionaire(): JSX.Element { setOrgDetails({ ...orgDetails, - organisationName: org[0].name, + organisationName: org[0].displayName, }); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/frontend/src/container/OnboardingV2Container/AddDataSource/AddDataSource.tsx b/frontend/src/container/OnboardingV2Container/AddDataSource/AddDataSource.tsx index 779c32f9d2..28cc8fa5ae 100644 --- a/frontend/src/container/OnboardingV2Container/AddDataSource/AddDataSource.tsx +++ b/frontend/src/container/OnboardingV2Container/AddDataSource/AddDataSource.tsx @@ -390,7 +390,7 @@ function OnboardingAddDataSource(): JSX.Element { setSetupStepItems([ { ...setupStepItemsBase[0], - description: org?.[0]?.name || '', + description: org?.[0]?.displayName || '', }, ...setupStepItemsBase.slice(1), ]); @@ -403,7 +403,7 @@ function OnboardingAddDataSource(): JSX.Element { setSetupStepItems([ { ...setupStepItemsBase[0], - description: org?.[0]?.name || '', + description: org?.[0]?.displayName || '', }, { ...setupStepItemsBase[1], @@ -415,7 +415,7 @@ function OnboardingAddDataSource(): JSX.Element { setSetupStepItems([ { ...setupStepItemsBase[0], - description: org?.[0]?.name || '', + description: org?.[0]?.displayName || '', }, { ...setupStepItemsBase[1], diff --git a/frontend/src/container/OrganizationSettings/DisplayName/index.tsx b/frontend/src/container/OrganizationSettings/DisplayName/index.tsx index 65cc6f585d..8ddcb91eeb 100644 --- a/frontend/src/container/OrganizationSettings/DisplayName/index.tsx +++ b/frontend/src/container/OrganizationSettings/DisplayName/index.tsx @@ -7,27 +7,22 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { requireErrorMessage } from 'utils/form/requireErrorMessage'; -function DisplayName({ - index, - id: orgId, - isAnonymous, -}: DisplayNameProps): JSX.Element { +function DisplayName({ index, id: orgId }: DisplayNameProps): JSX.Element { const [form] = Form.useForm(); - const orgName = Form.useWatch('name', form); + const orgName = Form.useWatch('displayName', form); const { t } = useTranslation(['organizationsettings', 'common']); const { org, updateOrg } = useAppContext(); - const { name } = (org || [])[index]; + const { displayName } = (org || [])[index]; const [isLoading, setIsLoading] = useState(false); const { notifications } = useNotifications(); const onSubmit = async (values: FormValues): Promise => { try { setIsLoading(true); - const { name } = values; + const { displayName } = values; const { statusCode, error } = await editOrg({ - isAnonymous, - name, + displayName, orgId, }); if (statusCode === 200) { @@ -36,7 +31,7 @@ function DisplayName({ ns: 'common', }), }); - updateOrg(orgId, name); + updateOrg(orgId, displayName); } else { notifications.error({ message: @@ -61,18 +56,18 @@ function DisplayName({ return
; } - const isDisabled = isLoading || orgName === name || !orgName; + const isDisabled = isLoading || orgName === displayName || !orgName; return (
@@ -95,11 +90,10 @@ function DisplayName({ interface DisplayNameProps { index: number; id: IUser['id']; - isAnonymous: boolean; } interface FormValues { - name: string; + displayName: string; } export default DisplayName; diff --git a/frontend/src/container/OrganizationSettings/index.tsx b/frontend/src/container/OrganizationSettings/index.tsx index d1980b3a4c..9e0ca10e73 100644 --- a/frontend/src/container/OrganizationSettings/index.tsx +++ b/frontend/src/container/OrganizationSettings/index.tsx @@ -23,12 +23,7 @@ function OrganizationSettings(): JSX.Element { <> {org.map((e, index) => ( - + ))} diff --git a/frontend/src/pages/SignUp/SignUp.tsx b/frontend/src/pages/SignUp/SignUp.tsx index 8307183e6e..4705705e88 100644 --- a/frontend/src/pages/SignUp/SignUp.tsx +++ b/frontend/src/pages/SignUp/SignUp.tsx @@ -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 getInviteDetails from 'api/user/getInviteDetails'; import loginApi from 'api/user/login'; @@ -14,13 +14,7 @@ import { useQuery } from 'react-query'; import { useLocation } from 'react-router-dom'; import { PayloadProps as LoginPrecheckPayloadProps } from 'types/api/user/loginPrecheck'; -import { - ButtonContainer, - FormContainer, - FormWrapper, - Label, - MarginTop, -} from './styles'; +import { ButtonContainer, FormContainer, FormWrapper, Label } from './styles'; import { isPasswordNotValidMessage, isPasswordValid } from './utils'; const { Title } = Typography; @@ -111,24 +105,15 @@ function SignUp({ version }: SignUpProps): JSX.Element { const isPreferenceVisible = token === null; - const commonHandler = async ( - values: FormValues, - isPreferenceVisible: boolean, - ): Promise => { + const commonHandler = async (values: FormValues): Promise => { try { const { organizationName, password, firstName, email } = values; const response = await signUpApi({ email, name: firstName, - orgName: organizationName, + orgDisplayName: organizationName, password, token: params.get('token') || undefined, - ...(isPreferenceVisible - ? { - isAnonymous: values.isAnonymous, - hasOptedUpdates: values.hasOptedUpdates, - } - : {}), }); if (response.statusCode === 200) { @@ -171,7 +156,7 @@ function SignUp({ version }: SignUpProps): JSX.Element { const response = await signUpApi({ email: values.email, name: values.firstName, - orgName: values.organizationName, + orgDisplayName: values.organizationName, password: values.password, token: params.get('token') || undefined, sourceUrl: encodeURIComponent(window.location.href), @@ -221,14 +206,14 @@ function SignUp({ version }: SignUpProps): JSX.Element { } if (isPreferenceVisible) { - await commonHandler(values, true); + await commonHandler(values); } else { logEvent('Account Created Successfully', { email: values.email, name: values.firstName, }); - await commonHandler(values, false); + await commonHandler(values); } setLoading(false); @@ -278,7 +263,6 @@ function SignUp({ version }: SignUpProps): JSX.Element { Create your account @@ -359,34 +343,6 @@ function SignUp({ version }: SignUpProps): JSX.Element { )}
)} - - {isPreferenceVisible && ( - <> - - - - - - - {t('prompt_keepme_posted')} - - - - - - - - - {t('prompt_anonymise')} - - - - )} - {isPreferenceVisible && ( => { body: JSON.stringify(loginApiResponse), }), ), - page.route(`**/org/${userLoginResponse.orgId}`, (route) => + page.route(`**/orgs/me`, (route) => route.fulfill({ status: 200, body: JSON.stringify(updateOrgResponse), diff --git a/pkg/modules/organization/implorganization/api.go b/pkg/modules/organization/implorganization/api.go new file mode 100644 index 0000000000..f899428946 --- /dev/null +++ b/pkg/modules/organization/implorganization/api.go @@ -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) +} diff --git a/pkg/modules/organization/implorganization/module.go b/pkg/modules/organization/implorganization/module.go new file mode 100644 index 0000000000..7162e2e6a1 --- /dev/null +++ b/pkg/modules/organization/implorganization/module.go @@ -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) +} diff --git a/pkg/modules/organization/implorganization/store.go b/pkg/modules/organization/implorganization/store.go new file mode 100644 index 0000000000..0c043adfca --- /dev/null +++ b/pkg/modules/organization/implorganization/store.go @@ -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 +} diff --git a/pkg/modules/organization/organization.go b/pkg/modules/organization/organization.go new file mode 100644 index 0000000000..fc2a445969 --- /dev/null +++ b/pkg/modules/organization/organization.go @@ -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) +} diff --git a/pkg/modules/preference/api.go b/pkg/modules/preference/api.go index 973fe24030..9bf704875b 100644 --- a/pkg/modules/preference/api.go +++ b/pkg/modules/preference/api.go @@ -15,7 +15,6 @@ type API interface { GetOrgPreference(http.ResponseWriter, *http.Request) UpdateOrgPreference(http.ResponseWriter, *http.Request) GetAllOrgPreferences(http.ResponseWriter, *http.Request) - GetUserPreference(http.ResponseWriter, *http.Request) UpdateUserPreference(http.ResponseWriter, *http.Request) GetAllUserPreferences(http.ResponseWriter, *http.Request) diff --git a/pkg/modules/preference/usecase.go b/pkg/modules/preference/usecase.go index d4b2c934ee..1d1e2a223d 100644 --- a/pkg/modules/preference/usecase.go +++ b/pkg/modules/preference/usecase.go @@ -10,7 +10,6 @@ type Usecase interface { GetOrgPreference(ctx context.Context, preferenceId string, orgId string) (*preferencetypes.GettablePreference, error) UpdateOrgPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, orgId string) error GetAllOrgPreferences(ctx context.Context, orgId string) ([]*preferencetypes.PreferenceWithValue, 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 GetAllUserPreferences(ctx context.Context, orgId string, userId string) ([]*preferencetypes.PreferenceWithValue, error) diff --git a/pkg/query-service/app/cloudintegrations/controller_test.go b/pkg/query-service/app/cloudintegrations/controller_test.go index b34ab9b83b..d5e4106fa4 100644 --- a/pkg/query-service/app/cloudintegrations/controller_test.go +++ b/pkg/query-service/app/cloudintegrations/controller_test.go @@ -4,6 +4,8 @@ import ( "context" "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/constants" "github.com/SigNoz/signoz/pkg/query-service/dao" @@ -20,7 +22,8 @@ func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) { controller, err := NewController(sqlStore) require.NoError(err) - user, apiErr := createTestUser() + organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore)) + user, apiErr := createTestUser(organizationModule) require.Nil(apiErr) // should be able to generate connection url for @@ -66,8 +69,8 @@ func TestAgentCheckIns(t *testing.T) { sqlStore := utils.NewQueryServiceDBForTests(t) controller, err := NewController(sqlStore) require.NoError(err) - - user, apiErr := createTestUser() + organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore)) + user, apiErr := createTestUser(organizationModule) require.Nil(apiErr) // 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 // connected for a particular cloud account id - _, apiErr = controller.DisconnectAccount( + _, _ = controller.DisconnectAccount( context.TODO(), user.OrgID, "aws", testAccountId1, ) @@ -153,7 +156,8 @@ func TestCantDisconnectNonExistentAccount(t *testing.T) { controller, err := NewController(sqlStore) require.NoError(err) - user, apiErr := createTestUser() + organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore)) + user, apiErr := createTestUser(organizationModule) require.Nil(apiErr) // Attempting to disconnect a non-existent account should return error @@ -171,7 +175,8 @@ func TestConfigureService(t *testing.T) { controller, err := NewController(sqlStore) require.NoError(err) - user, apiErr := createTestUser() + organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore)) + user, apiErr := createTestUser(organizationModule) require.Nil(apiErr) // create a connected account @@ -286,19 +291,18 @@ func makeTestConnectedAccount(t *testing.T, orgId string, controller *Controller return acc } -func createTestUser() (*types.User, *model.ApiError) { +func createTestUser(organizationModule organization.Module) (*types.User, *model.ApiError) { // Create a test user for auth ctx := context.Background() - org, apiErr := dao.DB().CreateOrg(ctx, &types.Organization{ - Name: "test", - }) - if apiErr != nil { - return nil, apiErr + organization := types.NewOrganization("test") + err := organizationModule.Create(ctx, organization) + if err != nil { + return nil, model.InternalError(err) } group, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup) if apiErr != nil { - return nil, apiErr + return nil, model.InternalError(apiErr) } auth.InitAuthCache(ctx) @@ -311,7 +315,7 @@ func createTestUser() (*types.User, *model.ApiError) { Name: "test", Email: userId[:8] + "test@test.com", Password: "test", - OrgID: org.ID, + OrgID: organization.ID.StringValue(), GroupID: group.ID, }, true, diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 06025dbe1c..a26ce2d4d2 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -22,6 +22,7 @@ import ( "github.com/SigNoz/signoz/pkg/apis/fields" errorsV2 "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/modules/preference" "github.com/SigNoz/signoz/pkg/query-service/app/integrations" "github.com/SigNoz/signoz/pkg/query-service/app/metricsexplorer" @@ -148,6 +149,9 @@ type APIHandler struct { Signoz *signoz.SigNoz Preference preference.API + + OrganizationAPI organization.API + OrganizationModule organization.Module } type APIHandlerOpts struct { @@ -196,7 +200,9 @@ type APIHandlerOpts struct { Signoz *signoz.SigNoz - Preference preference.API + Preference preference.API + OrganizationAPI organization.API + OrganizationModule organization.Module } // NewAPIHandler returns an APIHandler @@ -267,6 +273,8 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) { Signoz: opts.Signoz, Preference: opts.Preference, FieldsAPI: opts.FieldsAPI, + OrganizationAPI: opts.OrganizationAPI, + OrganizationModule: opts.OrganizationModule, } 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.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/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/resetPassword", am.OpenAccess(aH.resetPassword)).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) { token := mux.Vars(r)["token"] - resp, err := auth.GetInvite(context.Background(), token) + resp, err := auth.GetInvite(context.Background(), token, aH.OrganizationModule) if err != nil { RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorNotFound}, nil) 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. var resp []*model.InvitationResponseObject for _, inv := range invites { - - org, apiErr := dao.DB().GetOrg(ctx, inv.OrgID) - if apiErr != nil { - RespondError(w, apiErr, nil) + orgID, err := valuer.NewUUID(inv.OrgID) + if err != nil { + render.Error(w, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, "invalid org_id in the invite")) + } + 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{ Name: inv.Name, @@ -2124,7 +2136,7 @@ func (aH *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) { return } - _, apiErr := auth.Register(context.Background(), req, aH.Signoz.Alertmanager) + _, apiErr := auth.Register(context.Background(), req, aH.Signoz.Alertmanager, aH.OrganizationModule) if apiErr != nil { RespondError(w, apiErr, nil) 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) { - orgs, apiErr := dao.DB().GetOrgs(context.Background()) - if apiErr != nil { - RespondError(w, apiErr, "Failed to fetch orgs from the DB") - return - } - aH.WriteJSON(w, r, orgs) + aH.OrganizationAPI.GetAll(w, r) } func (aH *APIHandler) getOrg(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - 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) + aH.OrganizationAPI.Get(w, r) } -func (aH *APIHandler) editOrg(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - 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) updateOrg(w http.ResponseWriter, r *http.Request) { + aH.OrganizationAPI.Update(w, r) } func (aH *APIHandler) getOrgUsers(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/query-service/app/integrations/manager_test.go b/pkg/query-service/app/integrations/manager_test.go index a69c68d11a..689b60317a 100644 --- a/pkg/query-service/app/integrations/manager_test.go +++ b/pkg/query-service/app/integrations/manager_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/SigNoz/signoz/pkg/modules/organization/implorganization" _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/require" ) @@ -11,10 +12,11 @@ import ( func TestIntegrationLifecycle(t *testing.T) { require := require.New(t) - mgr := NewTestIntegrationsManager(t) + mgr, store := NewTestIntegrationsManager(t) ctx := context.Background() - user, apiErr := createTestUser() + organizationModule := implorganization.NewModule(implorganization.NewStore(store)) + user, apiErr := createTestUser(organizationModule) if apiErr != nil { t.Fatalf("could not create test user: %v", apiErr) } diff --git a/pkg/query-service/app/integrations/test_utils.go b/pkg/query-service/app/integrations/test_utils.go index dd731955c1..7494961ff4 100644 --- a/pkg/query-service/app/integrations/test_utils.go +++ b/pkg/query-service/app/integrations/test_utils.go @@ -5,19 +5,21 @@ import ( "slices" "testing" + "github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/query-service/auth" "github.com/SigNoz/signoz/pkg/query-service/constants" "github.com/SigNoz/signoz/pkg/query-service/dao" "github.com/SigNoz/signoz/pkg/query-service/model" v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3" "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/pipelinetypes" ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes" "github.com/google/uuid" ) -func NewTestIntegrationsManager(t *testing.T) *Manager { +func NewTestIntegrationsManager(t *testing.T) (*Manager, sqlstore.SQLStore) { testDB := utils.NewQueryServiceDBForTests(t) installedIntegrationsRepo, err := NewInstalledIntegrationsSqliteRepo(testDB) @@ -28,22 +30,21 @@ func NewTestIntegrationsManager(t *testing.T) *Manager { return &Manager{ availableIntegrationsRepo: &TestAvailableIntegrationsRepo{}, installedIntegrationsRepo: installedIntegrationsRepo, - } + }, testDB } -func createTestUser() (*types.User, *model.ApiError) { +func createTestUser(organizationModule organization.Module) (*types.User, *model.ApiError) { // Create a test user for auth ctx := context.Background() - org, apiErr := dao.DB().CreateOrg(ctx, &types.Organization{ - Name: "test", - }) - if apiErr != nil { - return nil, apiErr + organization := types.NewOrganization("test") + err := organizationModule.Create(ctx, organization) + if err != nil { + return nil, model.InternalError(err) } group, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup) if apiErr != nil { - return nil, apiErr + return nil, model.InternalError(apiErr) } auth.InitAuthCache(ctx) @@ -56,7 +57,7 @@ func createTestUser() (*types.User, *model.ApiError) { Name: "test", Email: userId[:8] + "test@test.com", Password: "test", - OrgID: org.ID, + OrgID: organization.ID.StringValue(), GroupID: group.ID, }, true, diff --git a/pkg/query-service/app/logparsingpipeline/db.go b/pkg/query-service/app/logparsingpipeline/db.go index d3cf79bf53..6703c62035 100644 --- a/pkg/query-service/app/logparsingpipeline/db.go +++ b/pkg/query-service/app/logparsingpipeline/db.go @@ -142,7 +142,7 @@ func (r *Repo) GetDefaultOrgID(ctx context.Context) (string, *model.ApiError) { if len(orgs) == 0 { 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) diff --git a/pkg/query-service/app/parser.go b/pkg/query-service/app/parser.go index 37576842d7..cedaa1dde3 100644 --- a/pkg/query-service/app/parser.go +++ b/pkg/query-service/app/parser.go @@ -536,14 +536,6 @@ func parseSetApdexScoreRequest(r *http.Request) (*types.ApdexSettings, error) { 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) { var req auth.RegisterRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { diff --git a/pkg/query-service/app/server.go b/pkg/query-service/app/server.go index dc96a75394..8cb3c46dda 100644 --- a/pkg/query-service/app/server.go +++ b/pkg/query-service/app/server.go @@ -15,6 +15,7 @@ import ( "github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/apis/fields" "github.com/SigNoz/signoz/pkg/http/middleware" + "github.com/SigNoz/signoz/pkg/modules/organization/implorganization" "github.com/SigNoz/signoz/pkg/modules/preference" preferencecore "github.com/SigNoz/signoz/pkg/modules/preference/core" "github.com/SigNoz/signoz/pkg/prometheus" @@ -185,7 +186,9 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) { } 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{ Reader: reader, SkipConfig: skipConfig, @@ -204,7 +207,9 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) { AlertmanagerAPI: alertmanager.NewAPI(serverOptions.SigNoz.Alertmanager), FieldsAPI: fields.NewAPI(serverOptions.SigNoz.TelemetryStore), Signoz: serverOptions.SigNoz, - Preference: preferenceModule, + Preference: preferenceAPI, + OrganizationAPI: organizationAPI, + OrganizationModule: organizationModule, }) if err != nil { return nil, err diff --git a/pkg/query-service/auth/auth.go b/pkg/query-service/auth/auth.go index d090c21403..10304732a6 100644 --- a/pkg/query-service/auth/auth.go +++ b/pkg/query-service/auth/auth.go @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" "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/dao" "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. -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)) 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") } - // TODO(Ahsan): This is not the best way to add org name in the invite response. We should - // either include org name in the invite table or do a join query. - org, apiErr := dao.DB().GetOrg(ctx, inv.OrgID) - if apiErr != nil { - return nil, errors.Wrap(apiErr.Err, "failed to query the DB") + orgID, err := valuer.NewUUID(inv.OrgID) + if err != nil { + return nil, err + } + org, err := organizationModule.Get(ctx, orgID) + if err != nil { + return nil, errors.Wrap(err, "failed to query the DB") } return &model.InvitationResponseObject{ Name: inv.Name, @@ -301,7 +304,7 @@ func GetInvite(ctx context.Context, token string) (*model.InvitationResponseObje Token: inv.Token, CreatedAt: inv.CreatedAt.Unix(), Role: inv.Role, - Organization: org.Name, + Organization: org.DisplayName, }, nil } @@ -390,21 +393,19 @@ func ChangePassword(ctx context.Context, req *model.ChangePasswordRequest) *mode } type RegisterRequest struct { - Name string `json:"name"` - OrgID string `json:"orgId"` - OrgName string `json:"orgName"` - Email string `json:"email"` - Password string `json:"password"` - InviteToken string `json:"token"` - IsAnonymous bool `json:"isAnonymous"` - HasOptedUpdates bool `json:"hasOptedUpdates"` + Name string `json:"name"` + OrgID string `json:"orgId"` + OrgDisplayName string `json:"orgDisplayName"` + OrgName string `json:"orgName"` + Email string `json:"email"` + Password string `json:"password"` + InviteToken string `json:"token"` // reference URL to track where the register request is coming from 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 == "" { return nil, model.BadRequest(model.ErrEmailRequired{}) } @@ -414,13 +415,10 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*types.User, } groupName := constants.AdminGroup - - // modify this to use bun - org, apierr := dao.DB().CreateOrg(ctx, - &types.Organization{Name: req.OrgName, IsAnonymous: req.IsAnonymous, HasOptedUpdates: req.HasOptedUpdates}) - if apierr != nil { - zap.L().Error("CreateOrg failed", zap.Error(apierr.ToError())) - return nil, apierr + organization := types.NewOrganization(req.OrgDisplayName) + err := organizationModule.Create(ctx, organization) + if err != nil { + return nil, model.InternalError(err) } group, apiErr := dao.DB().GetGroupByName(ctx, groupName) @@ -430,8 +428,6 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*types.User, } var hash string - var err error - hash, err = PasswordHash(req.Password) if err != nil { 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 GroupID: group.ID, - OrgID: org.ID, + OrgID: organization.ID.StringValue(), } 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 // and also the first registration is an enforced ADMIN registration. Every subsequent request will // 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) if err != nil { 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) { case 0: - user, err := RegisterFirstUser(ctx, req) + user, err := RegisterFirstUser(ctx, req, organizationModule) if err != nil { return nil, err } diff --git a/pkg/query-service/dao/interface.go b/pkg/query-service/dao/interface.go index 21dc8e93de..a1fdb9ed51 100644 --- a/pkg/query-service/dao/interface.go +++ b/pkg/query-service/dao/interface.go @@ -26,10 +26,6 @@ type Queries interface { GetGroupByName(ctx context.Context, name string) (*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) GetUsersByOrg(ctx context.Context, orgId 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) 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 DeleteResetPasswordEntry(ctx context.Context, token string) *model.ApiError diff --git a/pkg/query-service/dao/sqlite/connection.go b/pkg/query-service/dao/sqlite/connection.go index 739444e249..060deed42a 100644 --- a/pkg/query-service/dao/sqlite/connection.go +++ b/pkg/query-service/dao/sqlite/connection.go @@ -65,7 +65,7 @@ func (mds *ModelDaoSqlite) initializeOrgPreferences(ctx context.Context) error { } // set telemetry fields from userPreferences - telemetry.GetInstance().SetDistinctId(org.ID) + telemetry.GetInstance().SetDistinctId(org.ID.StringValue()) users, _ := mds.GetUsers(ctx) countUsers := len(users) diff --git a/pkg/query-service/dao/sqlite/rbac.go b/pkg/query-service/dao/sqlite/rbac.go index 01762320b5..1ac8f6de6a 100644 --- a/pkg/query-service/dao/sqlite/rbac.go +++ b/pkg/query-service/dao/sqlite/rbac.go @@ -3,7 +3,6 @@ package sqlite import ( "context" "fmt" - "time" "github.com/SigNoz/signoz/pkg/query-service/model" "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 } -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) { var orgs []types.Organization err := mds.bundb.NewSelect(). @@ -172,37 +106,6 @@ func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]types.Organization, * 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, user *types.User, isFirstUser bool) (*types.User, *model.ApiError) { _, err := mds.bundb.NewInsert(). @@ -306,7 +209,7 @@ func (mds *ModelDaoSqlite) GetUser(ctx context.Context, Table("users"). 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("o.name as organization"). + ColumnExpr("o.display_name as organization"). Join("JOIN groups g ON g.id = users.group_id"). Join("JOIN organizations o ON o.id = users.org_id"). Where("users.id = ?", id) @@ -343,7 +246,7 @@ func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context, Table("users"). 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("o.name as organization"). + ColumnExpr("o.display_name as organization"). Join("JOIN groups g ON g.id = users.group_id"). Join("JOIN organizations o ON o.id = users.org_id"). Where("users.email = ?", email) @@ -378,7 +281,7 @@ func (mds *ModelDaoSqlite) GetUsersWithOpts(ctx context.Context, limit int) ([]t Table("users"). 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("o.name as organization"). + ColumnExpr("o.display_name as organization"). Join("JOIN groups g ON g.id = users.group_id"). Join("JOIN organizations o ON o.id = users.org_id") @@ -402,7 +305,7 @@ func (mds *ModelDaoSqlite) GetUsersByOrg(ctx context.Context, Table("users"). 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("o.name as organization"). + ColumnExpr("o.display_name as organization"). Join("JOIN groups g ON g.id = users.group_id"). Join("JOIN organizations o ON o.id = users.org_id"). Where("users.org_id = ?", orgId) @@ -423,7 +326,7 @@ func (mds *ModelDaoSqlite) GetUsersByGroup(ctx context.Context, Table("users"). 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("o.name as organization"). + ColumnExpr("o.display_name as organization"). Join("JOIN groups g ON g.id = users.group_id"). Join("JOIN organizations o ON o.id = users.org_id"). Where("users.group_id = ?", groupId) diff --git a/pkg/query-service/tests/integration/filter_suggestions_test.go b/pkg/query-service/tests/integration/filter_suggestions_test.go index 58532e892f..f37264d2cc 100644 --- a/pkg/query-service/tests/integration/filter_suggestions_test.go +++ b/pkg/query-service/tests/integration/filter_suggestions_test.go @@ -10,6 +10,7 @@ import ( "testing" "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/auth" "github.com/SigNoz/signoz/pkg/query-service/constants" @@ -313,7 +314,8 @@ func NewFilterSuggestionsTestBed(t *testing.T) *FilterSuggestionsTestBed { apiHandler.RegisterRoutes(router, am) apiHandler.RegisterQueryRangeV3Routes(router, am) - user, apiErr := createTestUser() + organizationModule := implorganization.NewModule(implorganization.NewStore(testDB)) + user, apiErr := createTestUser(organizationModule) if apiErr != nil { t.Fatalf("could not create a test user: %v", apiErr) } diff --git a/pkg/query-service/tests/integration/logparsingpipeline_test.go b/pkg/query-service/tests/integration/logparsingpipeline_test.go index 8a4c5953f2..bf6e577d77 100644 --- a/pkg/query-service/tests/integration/logparsingpipeline_test.go +++ b/pkg/query-service/tests/integration/logparsingpipeline_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/SigNoz/signoz/pkg/modules/organization/implorganization" "github.com/SigNoz/signoz/pkg/query-service/agentConf" "github.com/SigNoz/signoz/pkg/query-service/app" "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) } - user, apiErr := createTestUser() + organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore)) + user, apiErr := createTestUser(organizationModule) if apiErr != nil { t.Fatalf("could not create a test user: %v", apiErr) } diff --git a/pkg/query-service/tests/integration/signoz_cloud_integrations_test.go b/pkg/query-service/tests/integration/signoz_cloud_integrations_test.go index 1a4268c90b..6f0bc47dc5 100644 --- a/pkg/query-service/tests/integration/signoz_cloud_integrations_test.go +++ b/pkg/query-service/tests/integration/signoz_cloud_integrations_test.go @@ -9,6 +9,8 @@ import ( "time" "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/cloudintegrations" "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.RegisterCloudIntegrationsRoutes(router, am) - user, apiErr := createTestUser() + organizationModule := implorganization.NewModule(implorganization.NewStore(testDB)) + user, apiErr := createTestUser(organizationModule) if apiErr != nil { t.Fatalf("could not create a test user: %v", apiErr) } diff --git a/pkg/query-service/tests/integration/signoz_integrations_test.go b/pkg/query-service/tests/integration/signoz_integrations_test.go index df79f532f7..cfd3154531 100644 --- a/pkg/query-service/tests/integration/signoz_integrations_test.go +++ b/pkg/query-service/tests/integration/signoz_integrations_test.go @@ -9,6 +9,7 @@ import ( "time" "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/cloudintegrations" "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.RegisterIntegrationRoutes(router, am) - user, apiErr := createTestUser() + organizationModule := implorganization.NewModule(implorganization.NewStore(testDB)) + user, apiErr := createTestUser(organizationModule) if apiErr != nil { t.Fatalf("could not create a test user: %v", apiErr) } diff --git a/pkg/query-service/tests/integration/test_utils.go b/pkg/query-service/tests/integration/test_utils.go index dc550a33fd..53bbf4a670 100644 --- a/pkg/query-service/tests/integration/test_utils.go +++ b/pkg/query-service/tests/integration/test_utils.go @@ -14,6 +14,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" "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/prometheustest" "github.com/SigNoz/signoz/pkg/query-service/app" @@ -148,19 +149,18 @@ func makeTestSignozLog( return testLog } -func createTestUser() (*types.User, *model.ApiError) { +func createTestUser(organizationModule organization.Module) (*types.User, *model.ApiError) { // Create a test user for auth ctx := context.Background() - org, apiErr := dao.DB().CreateOrg(ctx, &types.Organization{ - Name: "test", - }) - if apiErr != nil { - return nil, apiErr + organization := types.NewOrganization("test") + err := organizationModule.Create(ctx, organization) + if err != nil { + return nil, model.InternalError(err) } group, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup) if apiErr != nil { - return nil, apiErr + return nil, model.InternalError(apiErr) } auth.InitAuthCache(ctx) @@ -174,7 +174,7 @@ func createTestUser() (*types.User, *model.ApiError) { Name: "test", Email: userId[:8] + "test@test.com", Password: "test", - OrgID: org.ID, + OrgID: organization.ID.StringValue(), GroupID: group.ID, }, true, diff --git a/pkg/query-service/utils/testutils.go b/pkg/query-service/utils/testutils.go index e7fbd4a759..d14fac4d99 100644 --- a/pkg/query-service/utils/testutils.go +++ b/pkg/query-service/utils/testutils.go @@ -57,6 +57,7 @@ func NewTestSqliteDB(t *testing.T) (sqlStore sqlstore.SQLStore, testDBFilePath s sqlmigration.NewUpdatePatFactory(sqlStore), sqlmigration.NewAddVirtualFieldsFactory(), sqlmigration.NewUpdateIntegrationsFactory(sqlStore), + sqlmigration.NewUpdateOrganizationsFactory(sqlStore), ), ) if err != nil { diff --git a/pkg/signoz/provider.go b/pkg/signoz/provider.go index f0e00372dd..d3ae5e0886 100644 --- a/pkg/signoz/provider.go +++ b/pkg/signoz/provider.go @@ -72,6 +72,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM sqlmigration.NewUpdateRulesFactory(sqlstore), sqlmigration.NewAddVirtualFieldsFactory(), sqlmigration.NewUpdateIntegrationsFactory(sqlstore), + sqlmigration.NewUpdateOrganizationsFactory(sqlstore), ) } diff --git a/pkg/sqlmigration/028_update_organizations.go b/pkg/sqlmigration/028_update_organizations.go new file mode 100644 index 0000000000..ef67ccc8e4 --- /dev/null +++ b/pkg/sqlmigration/028_update_organizations.go @@ -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 +} diff --git a/pkg/sqlstore/sqlitesqlstore/dialect.go b/pkg/sqlstore/sqlitesqlstore/dialect.go index 964644c522..268358a6a4 100644 --- a/pkg/sqlstore/sqlitesqlstore/dialect.go +++ b/pkg/sqlstore/sqlitesqlstore/dialect.go @@ -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 { + 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) if err != nil { return err @@ -141,6 +149,26 @@ func (dialect *dialect) ColumnExists(ctx context.Context, bun bun.IDB, table str 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) { oldColumnExists, err := dialect.ColumnExists(ctx, bun, table, oldColumnName) if err != nil { @@ -152,10 +180,14 @@ func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table str return false, err } - if !oldColumnExists && newColumnExists { + if newColumnExists { return true, nil } + if !oldColumnExists { + return false, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "old column: %s doesn't exist", oldColumnName) + } + _, err = bun. ExecContext(ctx, "ALTER TABLE "+table+" RENAME COLUMN "+oldColumnName+" TO "+newColumnName) if err != nil { @@ -164,6 +196,26 @@ func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table str 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) { count := 0 diff --git a/pkg/sqlstore/sqlstore.go b/pkg/sqlstore/sqlstore.go index 6a9513e80a..8f4fead315 100644 --- a/pkg/sqlstore/sqlstore.go +++ b/pkg/sqlstore/sqlstore.go @@ -42,7 +42,9 @@ type SQLDialect interface { AddNotNullDefaultToColumn(context.Context, bun.IDB, string, string, string, string) error GetColumnType(context.Context, bun.IDB, string, string) (string, 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) + DropColumn(context.Context, bun.IDB, string, string) 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 AddPrimaryKey(context.Context, bun.IDB, interface{}, interface{}, string, func(context.Context) error) error diff --git a/pkg/sqlstore/sqlstoretest/dialect.go b/pkg/sqlstore/sqlstoretest/dialect.go index f30a3c7915..d14926c3db 100644 --- a/pkg/sqlstore/sqlstoretest/dialect.go +++ b/pkg/sqlstore/sqlstoretest/dialect.go @@ -25,10 +25,18 @@ func (dialect *dialect) ColumnExists(ctx context.Context, bun bun.IDB, table str 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) { 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 { return nil } diff --git a/pkg/types/organization.go b/pkg/types/organization.go index a31a7b13c6..c403c161cc 100644 --- a/pkg/types/organization.go +++ b/pkg/types/organization.go @@ -1,17 +1,34 @@ package types import ( + "context" + "time" + + "github.com/SigNoz/signoz/pkg/valuer" "github.com/uptrace/bun" ) -// TODO: check constraints are not working type Organization struct { bun.BaseModel `bun:"table:organizations"` TimeAuditable - ID string `bun:"id,pk,type:text" json:"id"` - Name string `bun:"name,type:text,notnull" json:"name"` - IsAnonymous bool `bun:"is_anonymous,notnull,default:0,CHECK(is_anonymous IN (0,1))" json:"isAnonymous"` - HasOptedUpdates bool `bun:"has_opted_updates,notnull,default:1,CHECK(has_opted_updates IN (0,1))" json:"hasOptedUpdates"` + Identifiable + Name string `bun:"name,type:text,nullzero" json:"name"` + Alias string `bun:"alias,type:text,nullzero" json:"alias"` + 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 { @@ -22,3 +39,11 @@ type ApdexSettings struct { Threshold float64 `bun:"threshold,type:float,notnull" json:"threshold"` 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 +}