mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
feat: disallow clone toggle on change request enabled (#3383)
This commit is contained in:
parent
663f26b712
commit
2caab45801
@ -18,6 +18,7 @@ import { getTogglePath } from 'utils/routePathHelpers';
|
||||
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
|
||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { useChangeRequestsEnabled } from '../../../hooks/useChangeRequestsEnabled';
|
||||
|
||||
const StyledPage = styled(Paper)(({ theme }) => ({
|
||||
overflow: 'visible',
|
||||
@ -65,6 +66,8 @@ export const CopyFeatureToggle = () => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const { feature } = useFeature(projectId, featureId);
|
||||
const navigate = useNavigate();
|
||||
const { isChangeRequestConfiguredInAnyEnv } =
|
||||
useChangeRequestsEnabled(projectId);
|
||||
|
||||
const setValue: ChangeEventHandler<HTMLInputElement> = event => {
|
||||
const value = trim(event.target.value);
|
||||
@ -152,7 +155,12 @@ export const CopyFeatureToggle = () => {
|
||||
label="Replace groupId"
|
||||
/>
|
||||
|
||||
<Button type="submit" color="primary" variant="contained">
|
||||
<Button
|
||||
type="submit"
|
||||
color="primary"
|
||||
variant="contained"
|
||||
disabled={isChangeRequestConfiguredInAnyEnv()}
|
||||
>
|
||||
<FileCopy />
|
||||
Create from copy
|
||||
</Button>
|
||||
|
@ -10,4 +10,5 @@ export interface IChangeRequestAccessReadModel {
|
||||
project: string,
|
||||
environment: string,
|
||||
): Promise<boolean>;
|
||||
isChangeRequestsEnabledForProject(project: string): Promise<boolean>;
|
||||
}
|
||||
|
@ -19,4 +19,8 @@ export class FakeChangeRequestAccessReadModel
|
||||
public async isChangeRequestsEnabled(): Promise<boolean> {
|
||||
return this.isChangeRequestEnabled;
|
||||
}
|
||||
|
||||
public async isChangeRequestsEnabledForProject(): Promise<boolean> {
|
||||
return this.isChangeRequestEnabled;
|
||||
}
|
||||
}
|
||||
|
@ -49,4 +49,18 @@ export class ChangeRequestAccessReadModel
|
||||
const { present } = result.rows[0];
|
||||
return present;
|
||||
}
|
||||
|
||||
public async isChangeRequestsEnabledForProject(
|
||||
project: string,
|
||||
): Promise<boolean> {
|
||||
const result = await this.db.raw(
|
||||
`SELECT EXISTS(SELECT 1
|
||||
FROM change_request_settings
|
||||
WHERE project = ?
|
||||
) AS present`,
|
||||
[project],
|
||||
);
|
||||
const { present } = result.rows[0];
|
||||
return present;
|
||||
}
|
||||
}
|
||||
|
@ -882,6 +882,15 @@ class FeatureToggleService {
|
||||
replaceGroupId: boolean = true, // eslint-disable-line
|
||||
userName: string,
|
||||
): Promise<FeatureToggle> {
|
||||
const changeRequestEnabled =
|
||||
await this.changeRequestAccessReadModel.isChangeRequestsEnabledForProject(
|
||||
projectId,
|
||||
);
|
||||
if (changeRequestEnabled) {
|
||||
throw new NoAccessError(
|
||||
`Cloning not allowed. Project ${projectId} has change requests enabled.`,
|
||||
);
|
||||
}
|
||||
this.logger.info(
|
||||
`${userName} clones feature toggle ${featureName} to ${newFeatureName}`,
|
||||
);
|
||||
@ -1754,7 +1763,7 @@ class FeatureToggleService {
|
||||
project: string,
|
||||
environment: string,
|
||||
featureName: string,
|
||||
user: User,
|
||||
user?: User,
|
||||
) {
|
||||
const hasEnvironment =
|
||||
await this.featureEnvironmentStore.featureHasEnvironment(
|
||||
|
@ -83,14 +83,18 @@ const updateStrategy = async (
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('feature_strategy_api_serial', getLogger);
|
||||
app = await setupAppWithCustomConfig(db.stores, {
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
bulkOperations: true,
|
||||
app = await setupAppWithCustomConfig(
|
||||
db.stores,
|
||||
{
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
bulkOperations: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
db.rawDatabase,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
@ -29,6 +29,8 @@ const mockConstraints = (): IConstraint[] => {
|
||||
}));
|
||||
};
|
||||
|
||||
const irrelevantDate = new Date();
|
||||
|
||||
beforeAll(async () => {
|
||||
const config = createTestConfig();
|
||||
db = await dbInit(
|
||||
@ -54,9 +56,14 @@ beforeAll(async () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await db.rawDatabase('change_request_settings').del();
|
||||
await db.destroy();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.rawDatabase('change_request_settings').del();
|
||||
});
|
||||
|
||||
test('Should create feature toggle strategy configuration', async () => {
|
||||
const projectId = 'default';
|
||||
const username = 'feature-toggle';
|
||||
@ -263,7 +270,7 @@ test('adding and removing an environment preserves variants when variants per en
|
||||
|
||||
const toggle = await service.getFeature({
|
||||
featureName,
|
||||
projectId: null,
|
||||
projectId: undefined,
|
||||
environmentVariants: false,
|
||||
});
|
||||
expect(toggle.variants).toHaveLength(1);
|
||||
@ -327,6 +334,26 @@ test('cloning a feature toggle copies variant environments correctly', async ()
|
||||
expect(newEnv.variants).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('cloning a feature toggle not allowed for change requests enabled', async () => {
|
||||
await db.rawDatabase('change_request_settings').insert({
|
||||
project: 'default',
|
||||
environment: 'default',
|
||||
});
|
||||
await expect(
|
||||
service.cloneFeatureToggle(
|
||||
'newToggleName',
|
||||
'default',
|
||||
'clonedToggleName',
|
||||
true,
|
||||
'test-user',
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new NoAccessError(
|
||||
`Cloning not allowed. Project default has change requests enabled.`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('Cloning a feature toggle also clones segments correctly', async () => {
|
||||
const featureName = 'ToggleWithSegments';
|
||||
const clonedFeatureName = 'AWholeNewFeatureToggle';
|
||||
@ -372,7 +399,7 @@ test('Cloning a feature toggle also clones segments correctly', async () => {
|
||||
|
||||
let feature = await service.getFeature({ featureName: clonedFeatureName });
|
||||
expect(
|
||||
feature.environments.find((x) => x.name === 'default').strategies[0]
|
||||
feature.environments.find((x) => x.name === 'default')?.strategies[0]
|
||||
.segments,
|
||||
).toHaveLength(1);
|
||||
});
|
||||
@ -425,14 +452,14 @@ test('If change requests are enabled, cannot change variants without going via C
|
||||
'default',
|
||||
[newVariant],
|
||||
{
|
||||
createdAt: undefined,
|
||||
createdAt: irrelevantDate,
|
||||
email: '',
|
||||
id: 0,
|
||||
imageUrl: '',
|
||||
loginAttempts: 0,
|
||||
name: '',
|
||||
permissions: [],
|
||||
seenAt: undefined,
|
||||
seenAt: irrelevantDate,
|
||||
username: '',
|
||||
generateImageUrl(): string {
|
||||
return '';
|
||||
@ -532,14 +559,14 @@ test('If CRs are protected for any environment in the project stops bulk update
|
||||
[enabledEnv.name, disabledEnv.name],
|
||||
newVariants,
|
||||
{
|
||||
createdAt: undefined,
|
||||
createdAt: irrelevantDate,
|
||||
email: '',
|
||||
id: 0,
|
||||
imageUrl: '',
|
||||
loginAttempts: 0,
|
||||
name: '',
|
||||
permissions: [],
|
||||
seenAt: undefined,
|
||||
seenAt: irrelevantDate,
|
||||
username: '',
|
||||
generateImageUrl(): string {
|
||||
return '';
|
||||
|
Loading…
Reference in New Issue
Block a user