Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Environment variables in admin panel (read only) - backend #9943

Merged
merged 24 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
49a2dfe
decorators and enum
ehconitin Jan 30, 2025
0d798a7
WIP: backend - env getAll, new method in admin for grouping
ehconitin Jan 30, 2025
96bbf4e
remove extra isDefined
ehconitin Jan 30, 2025
77c0bd0
missing async
ehconitin Jan 30, 2025
4addc34
typo
ehconitin Jan 30, 2025
ddcc906
review
ehconitin Jan 31, 2025
b10264e
remove includeSensitive arg
ehconitin Jan 31, 2025
5b67dd5
Merge remote-tracking branch 'upstream/main' into env-var-backend
ehconitin Jan 31, 2025
7154b6d
positions for groups and alphabetical sorting for subgroups
ehconitin Jan 31, 2025
111ed1d
masking of APP_SECRET, REDIS_URL and POSTGRES_URL
ehconitin Jan 31, 2025
c51656c
Merge remote-tracking branch 'upstream/main' into env-var-backend
ehconitin Jan 31, 2025
fde7ffc
remove log
ehconitin Jan 31, 2025
214d39f
grep :)
ehconitin Jan 31, 2025
5eec6c3
Merge remote-tracking branch 'upstream/main' into env-var-backend
ehconitin Jan 31, 2025
532d7dc
unit tests for getEnvironmentVariablesGrouped
ehconitin Jan 31, 2025
5801cb9
one file per dto
ehconitin Jan 31, 2025
39a1565
unit test for position const
ehconitin Jan 31, 2025
eb7ad78
unit test on environment gatAll()
ehconitin Jan 31, 2025
bceb8b5
tests on util
ehconitin Jan 31, 2025
ed98fd2
flag redis url sensitive
ehconitin Jan 31, 2025
a18bddb
Merge branch 'main' into env-var-backend
FelixMalfait Feb 2, 2025
a12bc2f
Merge remote-tracking branch 'upstream/main' into env-var-backend
ehconitin Feb 3, 2025
e5411f1
add hide const and some other review
ehconitin Feb 3, 2025
4f64017
Merge remote-tracking branch 'upstream/main' into env-var-backend
ehconitin Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions packages/twenty-front/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,69 @@ export type EmailPasswordResetLink = {
success: Scalars['Boolean'];
};

export type EnvironmentVariable = {
__typename?: 'EnvironmentVariable';
description: Scalars['String'];
name: Scalars['String'];
sensitive: Scalars['Boolean'];
value: Scalars['String'];
};

export enum EnvironmentVariablesGroup {
Analytics = 'Analytics',
Authentication = 'Authentication',
Billing = 'Billing',
Cache = 'Cache',
Database = 'Database',
Email = 'Email',
Frontend = 'Frontend',
LLM = 'LLM',
Logging = 'Logging',
QueueConfig = 'QueueConfig',
Security = 'Security',
ServerConfig = 'ServerConfig',
Serverless = 'Serverless',
Storage = 'Storage',
Support = 'Support',
Workspace = 'Workspace'
}

export type EnvironmentVariablesGroupData = {
__typename?: 'EnvironmentVariablesGroupData';
groupName: EnvironmentVariablesGroup;
subgroups: Array<EnvironmentVariablesSubgroupData>;
variables: Array<EnvironmentVariable>;
};

export type EnvironmentVariablesOutput = {
__typename?: 'EnvironmentVariablesOutput';
groups: Array<EnvironmentVariablesGroupData>;
};

export enum EnvironmentVariablesSubGroup {
CloudflareConfig = 'CloudflareConfig',
EmailSettings = 'EmailSettings',
FrontSupportConfig = 'FrontSupportConfig',
GoogleAuth = 'GoogleAuth',
LambdaConfig = 'LambdaConfig',
MicrosoftAuth = 'MicrosoftAuth',
PasswordAuth = 'PasswordAuth',
RateLimiting = 'RateLimiting',
S3Config = 'S3Config',
SSL = 'SSL',
SentryConfig = 'SentryConfig',
SmtpConfig = 'SmtpConfig',
StripeConfig = 'StripeConfig',
TinybirdConfig = 'TinybirdConfig',
Tokens = 'Tokens'
}

export type EnvironmentVariablesSubgroupData = {
__typename?: 'EnvironmentVariablesSubgroupData';
subgroupName: EnvironmentVariablesSubGroup;
variables: Array<EnvironmentVariable>;
};

export type ExecuteServerlessFunctionInput = {
/** Id of the serverless function to execute */
id: Scalars['UUID'];
Expand Down Expand Up @@ -1177,6 +1240,7 @@ export type Query = {
findWorkspaceFromInviteHash: Workspace;
findWorkspaceInvitations: Array<WorkspaceInvitation>;
getAvailablePackages: Scalars['JSON'];
getEnvironmentVariablesGrouped: EnvironmentVariablesOutput;
getHostnameDetails?: Maybe<CustomHostnameDetails>;
getPostgresCredentials?: Maybe<PostgresCredentials>;
getProductPrices: BillingProductPricesOutput;
Expand Down Expand Up @@ -2151,6 +2215,11 @@ export type UserLookupAdminPanelMutationVariables = Exact<{

export type UserLookupAdminPanelMutation = { __typename?: 'Mutation', userLookupAdminPanel: { __typename?: 'UserLookup', user: { __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }, workspaces: Array<{ __typename?: 'WorkspaceInfo', id: string, name: string, logo?: string | null, totalUsers: number, allowImpersonation: boolean, users: Array<{ __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }>, featureFlags: Array<{ __typename?: 'FeatureFlag', key: FeatureFlagKey, value: boolean }> }> } };

export type GetEnvironmentVariablesGroupedQueryVariables = Exact<{ [key: string]: never; }>;


export type GetEnvironmentVariablesGroupedQuery = { __typename?: 'Query', getEnvironmentVariablesGrouped: { __typename?: 'EnvironmentVariablesOutput', groups: Array<{ __typename?: 'EnvironmentVariablesGroupData', groupName: EnvironmentVariablesGroup, variables: Array<{ __typename?: 'EnvironmentVariable', name: string, description: string, value: string, sensitive: boolean }>, subgroups: Array<{ __typename?: 'EnvironmentVariablesSubgroupData', subgroupName: EnvironmentVariablesSubGroup, variables: Array<{ __typename?: 'EnvironmentVariable', name: string, description: string, value: string, sensitive: boolean }> }> }> } };

export type UpdateLabPublicFeatureFlagMutationVariables = Exact<{
input: UpdateLabPublicFeatureFlagInput;
}>;
Expand Down Expand Up @@ -3762,6 +3831,57 @@ export function useUserLookupAdminPanelMutation(baseOptions?: Apollo.MutationHoo
export type UserLookupAdminPanelMutationHookResult = ReturnType<typeof useUserLookupAdminPanelMutation>;
export type UserLookupAdminPanelMutationResult = Apollo.MutationResult<UserLookupAdminPanelMutation>;
export type UserLookupAdminPanelMutationOptions = Apollo.BaseMutationOptions<UserLookupAdminPanelMutation, UserLookupAdminPanelMutationVariables>;
export const GetEnvironmentVariablesGroupedDocument = gql`
query GetEnvironmentVariablesGrouped {
getEnvironmentVariablesGrouped {
groups {
groupName
variables {
name
description
value
sensitive
}
subgroups {
subgroupName
variables {
name
description
value
sensitive
}
}
}
}
}
`;

/**
* __useGetEnvironmentVariablesGroupedQuery__
*
* To run a query within a React component, call `useGetEnvironmentVariablesGroupedQuery` and pass it any options that fit your needs.
* When your component renders, `useGetEnvironmentVariablesGroupedQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetEnvironmentVariablesGroupedQuery({
* variables: {
* },
* });
*/
export function useGetEnvironmentVariablesGroupedQuery(baseOptions?: Apollo.QueryHookOptions<GetEnvironmentVariablesGroupedQuery, GetEnvironmentVariablesGroupedQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetEnvironmentVariablesGroupedQuery, GetEnvironmentVariablesGroupedQueryVariables>(GetEnvironmentVariablesGroupedDocument, options);
}
export function useGetEnvironmentVariablesGroupedLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetEnvironmentVariablesGroupedQuery, GetEnvironmentVariablesGroupedQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetEnvironmentVariablesGroupedQuery, GetEnvironmentVariablesGroupedQueryVariables>(GetEnvironmentVariablesGroupedDocument, options);
}
export type GetEnvironmentVariablesGroupedQueryHookResult = ReturnType<typeof useGetEnvironmentVariablesGroupedQuery>;
export type GetEnvironmentVariablesGroupedLazyQueryHookResult = ReturnType<typeof useGetEnvironmentVariablesGroupedLazyQuery>;
export type GetEnvironmentVariablesGroupedQueryResult = Apollo.QueryResult<GetEnvironmentVariablesGroupedQuery, GetEnvironmentVariablesGroupedQueryVariables>;
export const UpdateLabPublicFeatureFlagDocument = gql`
mutation UpdateLabPublicFeatureFlag($input: UpdateLabPublicFeatureFlagInput!) {
updateLabPublicFeatureFlag(input: $input)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { gql } from '@apollo/client';

export const GET_ENVIRONMENT_VARIABLES_GROUPED = gql`
query GetEnvironmentVariablesGrouped {
getEnvironmentVariablesGrouped {
groups {
groupName
variables {
name
description
value
sensitive
}
subgroups {
subgroupName
variables {
name
description
value
sensitive
}
}
}
}
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception';
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
import { EnvironmentVariablesGroup } from 'src/engine/core-modules/environment/enums/environment-variables-group.enum';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { User } from 'src/engine/core-modules/user/user.entity';
Expand All @@ -17,6 +19,7 @@ const WorkspaceFindOneMock = jest.fn();
const FeatureFlagUpdateMock = jest.fn();
const FeatureFlagSaveMock = jest.fn();
const LoginTokenServiceGenerateLoginTokenMock = jest.fn();
const EnvironmentServiceGetAllMock = jest.fn();

jest.mock(
'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum',
Expand All @@ -29,6 +32,13 @@ jest.mock(
},
);

jest.mock(
'src/engine/core-modules/environment/constants/environment-variables-hidden-groups',
() => ({
ENVIRONMENT_VARIABLES_HIDDEN_GROUPS: new Set(['HIDDEN_GROUP']),
}),
);

describe('AdminPanelService', () => {
let service: AdminPanelService;

Expand Down Expand Up @@ -61,6 +71,12 @@ describe('AdminPanelService', () => {
generateLoginToken: LoginTokenServiceGenerateLoginTokenMock,
},
},
{
provide: EnvironmentService,
useValue: {
getAll: EnvironmentServiceGetAllMock,
},
},
],
}).compile();

Expand Down Expand Up @@ -214,4 +230,144 @@ describe('AdminPanelService', () => {

expect(UserFindOneMock).toHaveBeenCalled();
});

describe('getEnvironmentVariablesGrouped', () => {
it('should correctly group environment variables', () => {
EnvironmentServiceGetAllMock.mockReturnValue({
VAR_1: {
value: 'value1',
metadata: {
group: 'GROUP_1',
description: 'Description 1',
},
},
VAR_2: {
value: 'value2',
metadata: {
group: 'GROUP_1',
subGroup: 'SUBGROUP_1',
description: 'Description 2',
sensitive: true,
},
},
VAR_3: {
value: 'value3',
metadata: {
group: 'GROUP_2',
description: 'Description 3',
},
},
});

const result = service.getEnvironmentVariablesGrouped();

expect(result).toEqual({
groups: expect.arrayContaining([
expect.objectContaining({
groupName: 'GROUP_1',
variables: [
{
name: 'VAR_1',
value: 'value1',
description: 'Description 1',
sensitive: false,
},
],
subgroups: [
{
subgroupName: 'SUBGROUP_1',
variables: [
{
name: 'VAR_2',
value: 'value2',
description: 'Description 2',
sensitive: true,
},
],
},
],
}),
expect.objectContaining({
groupName: 'GROUP_2',
variables: [
{
name: 'VAR_3',
value: 'value3',
description: 'Description 3',
sensitive: false,
},
],
subgroups: [],
}),
]),
});
});

it('should sort groups by position and variables alphabetically', () => {
EnvironmentServiceGetAllMock.mockReturnValue({
Z_VAR: {
value: 'valueZ',
metadata: {
group: 'GROUP_1',
description: 'Description Z',
},
},
A_VAR: {
value: 'valueA',
metadata: {
group: 'GROUP_1',
description: 'Description A',
},
},
});

const result = service.getEnvironmentVariablesGrouped();

const group = result.groups.find(
(g) => g.groupName === ('GROUP_1' as EnvironmentVariablesGroup),
);

expect(group?.variables[0].name).toBe('A_VAR');
expect(group?.variables[1].name).toBe('Z_VAR');
});

it('should handle empty environment variables', () => {
EnvironmentServiceGetAllMock.mockReturnValue({});

const result = service.getEnvironmentVariablesGrouped();

expect(result).toEqual({
groups: [],
});
});

it('should exclude hidden groups from the output', () => {
EnvironmentServiceGetAllMock.mockReturnValue({
VAR_1: {
value: 'value1',
metadata: {
group: 'HIDDEN_GROUP',
description: 'Description 1',
},
},
VAR_2: {
value: 'value2',
metadata: {
group: 'VISIBLE_GROUP',
description: 'Description 2',
},
},
});

const result = service.getEnvironmentVariablesGrouped();

expect(result.groups).toHaveLength(1);
expect(result.groups[0].groupName).toBe('VISIBLE_GROUP');
expect(result.groups).not.toContainEqual(
expect.objectContaining({
groupName: 'HIDDEN_GROUP',
}),
);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { UseFilters, UseGuards } from '@nestjs/common';
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';

import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
import { EnvironmentVariablesOutput } from 'src/engine/core-modules/admin-panel/dtos/environment-variables.output';
import { ImpersonateInput } from 'src/engine/core-modules/admin-panel/dtos/impersonate.input';
import { ImpersonateOutput } from 'src/engine/core-modules/admin-panel/dtos/impersonate.output';
import { UpdateWorkspaceFeatureFlagInput } from 'src/engine/core-modules/admin-panel/dtos/update-workspace-feature-flag.input';
Expand Down Expand Up @@ -46,4 +47,10 @@ export class AdminPanelResolver {

return true;
}

@UseGuards(WorkspaceAuthGuard, UserAuthGuard, ImpersonateGuard)
@Query(() => EnvironmentVariablesOutput)
async getEnvironmentVariablesGrouped(): Promise<EnvironmentVariablesOutput> {
return this.adminService.getEnvironmentVariablesGrouped();
ehconitin marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading
Loading