mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-10 07:09:00 +08:00
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:
parent
a1846c008a
commit
c322657666
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<SuccessResponse<PayloadProps> | 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,
|
||||
|
@ -160,7 +160,7 @@ export default function CustomDomainSettings(): JSX.Element {
|
||||
{!isLoadingDeploymentsData && (
|
||||
<Card className="custom-domain-settings-card">
|
||||
<div className="custom-domain-settings-content-header">
|
||||
Team {org?.[0]?.name} Information
|
||||
Team {org?.[0]?.displayName} Information
|
||||
</div>
|
||||
|
||||
<div className="custom-domain-settings-content-body">
|
||||
|
@ -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', {
|
||||
|
@ -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
|
||||
|
@ -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],
|
||||
|
@ -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<FormValues>();
|
||||
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<boolean>(false);
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onSubmit = async (values: FormValues): Promise<void> => {
|
||||
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 <div />;
|
||||
}
|
||||
|
||||
const isDisabled = isLoading || orgName === name || !orgName;
|
||||
const isDisabled = isLoading || orgName === displayName || !orgName;
|
||||
|
||||
return (
|
||||
<Form
|
||||
initialValues={{ name }}
|
||||
initialValues={{ displayName }}
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={onSubmit}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
name="name"
|
||||
name="displayName"
|
||||
label="Display name"
|
||||
rules={[{ required: true, message: requireErrorMessage('Display name') }]}
|
||||
>
|
||||
@ -95,11 +90,10 @@ function DisplayName({
|
||||
interface DisplayNameProps {
|
||||
index: number;
|
||||
id: IUser['id'];
|
||||
isAnonymous: boolean;
|
||||
}
|
||||
|
||||
interface FormValues {
|
||||
name: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
export default DisplayName;
|
||||
|
@ -23,12 +23,7 @@ function OrganizationSettings(): JSX.Element {
|
||||
<>
|
||||
<Space direction="vertical">
|
||||
{org.map((e, index) => (
|
||||
<DisplayName
|
||||
isAnonymous={e.isAnonymous}
|
||||
key={e.id}
|
||||
id={e.id}
|
||||
index={index}
|
||||
/>
|
||||
<DisplayName key={e.id} id={e.id} index={index} />
|
||||
))}
|
||||
</Space>
|
||||
<Divider />
|
||||
|
@ -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<void> => {
|
||||
const commonHandler = async (values: FormValues): Promise<void> => {
|
||||
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 {
|
||||
<FormContainer
|
||||
onFinish={!precheck.sso ? handleSubmit : handleSubmitSSO}
|
||||
onValuesChange={handleValuesChange}
|
||||
initialValues={{ hasOptedUpdates: true, isAnonymous: false }}
|
||||
form={form}
|
||||
>
|
||||
<Title level={4}>Create your account</Title>
|
||||
@ -359,34 +343,6 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
)}
|
||||
</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 && (
|
||||
<Typography.Paragraph
|
||||
italic
|
||||
|
@ -82,10 +82,8 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
return [
|
||||
{
|
||||
createdAt: 0,
|
||||
hasOptedUpdates: false,
|
||||
id: userData.payload.orgId,
|
||||
isAnonymous: false,
|
||||
name: userData.payload.organization,
|
||||
displayName: userData.payload.organization,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -95,10 +93,8 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
...prev.slice(0, orgIndex),
|
||||
{
|
||||
createdAt: 0,
|
||||
hasOptedUpdates: false,
|
||||
id: userData.payload.orgId,
|
||||
isAnonymous: false,
|
||||
name: userData.payload.organization,
|
||||
displayName: userData.payload.organization,
|
||||
},
|
||||
...prev.slice(orgIndex + 1, prev.length),
|
||||
];
|
||||
@ -209,10 +205,8 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
...org.slice(0, orgIndex),
|
||||
{
|
||||
createdAt: 0,
|
||||
hasOptedUpdates: false,
|
||||
id: orgId,
|
||||
isAnonymous: false,
|
||||
name: updatedOrgName,
|
||||
displayName: updatedOrgName,
|
||||
},
|
||||
...org.slice(orgIndex + 1, org.length),
|
||||
];
|
||||
|
@ -156,10 +156,8 @@ export function getAppContextMock(
|
||||
org: [
|
||||
{
|
||||
createdAt: 0,
|
||||
hasOptedUpdates: false,
|
||||
id: 'does-not-matter-id',
|
||||
isAnonymous: false,
|
||||
name: 'Pentagon',
|
||||
displayName: 'Pentagon',
|
||||
},
|
||||
],
|
||||
isFetchingUser: false,
|
||||
|
@ -1,8 +1,6 @@
|
||||
export interface Props {
|
||||
name: string;
|
||||
isAnonymous: boolean;
|
||||
displayName: string;
|
||||
orgId: string;
|
||||
hasOptedUpdates?: boolean;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
|
@ -14,6 +14,6 @@ export interface PayloadProps {
|
||||
name: User['name'];
|
||||
role: ROLES;
|
||||
token: string;
|
||||
organization: Organization['name'];
|
||||
organization: Organization['displayName'];
|
||||
precheck?: LoginPrecheckPayloadProps;
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
export interface Organization {
|
||||
createdAt: number;
|
||||
hasOptedUpdates: boolean;
|
||||
id: string;
|
||||
isAnonymous: boolean;
|
||||
name: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
export type PayloadProps = Organization[];
|
||||
|
@ -1,10 +1,8 @@
|
||||
export interface Props {
|
||||
name: string;
|
||||
orgName: string;
|
||||
orgDisplayName: string;
|
||||
email: string;
|
||||
password: string;
|
||||
token?: string;
|
||||
sourceUrl?: string;
|
||||
isAnonymous?: boolean;
|
||||
hasOptedUpdates?: boolean;
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ export type Created = 201;
|
||||
|
||||
export type Success = 200;
|
||||
|
||||
export type SuccessNoContent = 204;
|
||||
|
||||
export type Forbidden = 403;
|
||||
|
||||
export type BadRequest = 400;
|
||||
@ -14,7 +16,7 @@ export type Conflict = 409;
|
||||
|
||||
export type ServerError = 500;
|
||||
|
||||
export type SuccessStatusCode = Created | Success;
|
||||
export type SuccessStatusCode = Created | Success | SuccessNoContent;
|
||||
|
||||
export type ErrorStatusCode =
|
||||
| Forbidden
|
||||
|
2
frontend/tests/fixtures/common.ts
vendored
2
frontend/tests/fixtures/common.ts
vendored
@ -33,7 +33,7 @@ export const loginApi = async (page: Page): Promise<void> => {
|
||||
body: JSON.stringify(loginApiResponse),
|
||||
}),
|
||||
),
|
||||
page.route(`**/org/${userLoginResponse.orgId}`, (route) =>
|
||||
page.route(`**/orgs/me`, (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(updateOrgResponse),
|
||||
|
80
pkg/modules/organization/implorganization/api.go
Normal file
80
pkg/modules/organization/implorganization/api.go
Normal 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)
|
||||
}
|
33
pkg/modules/organization/implorganization/module.go
Normal file
33
pkg/modules/organization/implorganization/module.go
Normal 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)
|
||||
}
|
102
pkg/modules/organization/implorganization/store.go
Normal file
102
pkg/modules/organization/implorganization/store.go
Normal 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
|
||||
}
|
34
pkg/modules/organization/organization.go
Normal file
34
pkg/modules/organization/organization.go
Normal 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)
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -72,6 +72,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM
|
||||
sqlmigration.NewUpdateRulesFactory(sqlstore),
|
||||
sqlmigration.NewAddVirtualFieldsFactory(),
|
||||
sqlmigration.NewUpdateIntegrationsFactory(sqlstore),
|
||||
sqlmigration.NewUpdateOrganizationsFactory(sqlstore),
|
||||
)
|
||||
}
|
||||
|
||||
|
119
pkg/sqlmigration/028_update_organizations.go
Normal file
119
pkg/sqlmigration/028_update_organizations.go
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user