mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	chore: refactor Cypress tests (#3445)
Adds proper typescript support. Created reusable commands Added README for cypress test Refactored tests Fixed bugs as I found them. <!-- Thanks for creating a PR! To make it easier for reviewers and everyone else to understand what your changes relate to, please add some relevant content to the headings below. Feel free to ignore or delete sections that you don't think are relevant. Thank you! ❤️ --> ## About the changes <!-- Describe the changes introduced. What are they and why are they being introduced? Feel free to also add screenshots or steps to view the changes if they're visual. --> <!-- Does it close an issue? Multiple? --> Closes # <!-- (For internal contributors): Does it relate to an issue on public roadmap? --> <!-- Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item: # --> ### Important files <!-- PRs can contain a lot of changes, but not all changes are equally important. Where should a reviewer start looking to get an overview of the changes? Are any files particularly important? --> ## Discussion points <!-- Anything about the PR you'd like to discuss before it gets merged? Got any questions or doubts? --> --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
		
							parent
							
								
									7063112547
								
							
						
					
					
						commit
						99c555bf59
					
				
							
								
								
									
										2
									
								
								.github/workflows/e2e.frontend.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/e2e.frontend.yaml
									
									
									
									
										vendored
									
									
								
							@ -11,7 +11,7 @@ jobs:
 | 
				
			|||||||
          - groups/groups.spec.ts
 | 
					          - groups/groups.spec.ts
 | 
				
			||||||
          - projects/access.spec.ts
 | 
					          - projects/access.spec.ts
 | 
				
			||||||
          - projects/overview.spec.ts
 | 
					          - projects/overview.spec.ts
 | 
				
			||||||
          # - projects/settings.spec.ts
 | 
					          - projects/settings.spec.ts
 | 
				
			||||||
          - projects/notifications.spec.ts
 | 
					          - projects/notifications.spec.ts
 | 
				
			||||||
          - segments/segments.spec.ts
 | 
					          - segments/segments.spec.ts
 | 
				
			||||||
          - import/import.spec.ts
 | 
					          - import/import.spec.ts
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								frontend/cypress.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								frontend/cypress.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					/// <reference types="cypress" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare namespace Cypress {
 | 
				
			||||||
 | 
					    interface Chainable {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								frontend/cypress/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								frontend/cypress/README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					## Unleash Behavioural tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Add common commands to Cypress
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-   `global.d.ts` is where we extend Cypress types
 | 
				
			||||||
 | 
					-   `API.ts` contains api requests for common actions (great place for cleanup actions)
 | 
				
			||||||
 | 
					-   `UI.ts` contains common functions for UI operations
 | 
				
			||||||
 | 
					-   `commands.ts` is the place to map the functions to a cypress command
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Test Format
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ideally each test should manage its own data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Avoid using `after` and `afterEach` hooks for cleaning up. According to Cypress docs, there is no guarantee that the functions will run
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Suggested Format:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-   `prepare`
 | 
				
			||||||
 | 
					-   `when`
 | 
				
			||||||
 | 
					-   `then`
 | 
				
			||||||
 | 
					-   `clean`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Passing (returned) parameters around
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```ts
 | 
				
			||||||
 | 
					it('can add, update and delete a gradual rollout strategy to the development environment', async () => {
 | 
				
			||||||
 | 
					    cy.addFlexibleRolloutStrategyToFeature_UI({
 | 
				
			||||||
 | 
					        featureToggleName,
 | 
				
			||||||
 | 
					    }).then(value => {
 | 
				
			||||||
 | 
					        strategyId = value;
 | 
				
			||||||
 | 
					        cy.updateFlexibleRolloutStrategy_UI(featureToggleName, strategyId).then(
 | 
				
			||||||
 | 
					            () => cy.deleteFeatureStrategy_UI(featureToggleName, strategyId)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
							
								
								
									
										81
									
								
								frontend/cypress/global.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								frontend/cypress/global.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					/// <reference types="cypress" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare namespace Cypress {
 | 
				
			||||||
 | 
					    interface AddFlexibleRolloutStrategyOptions {
 | 
				
			||||||
 | 
					        featureToggleName: string;
 | 
				
			||||||
 | 
					        project?: string;
 | 
				
			||||||
 | 
					        environment?: string;
 | 
				
			||||||
 | 
					        stickiness?: string;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interface UserCredentials {
 | 
				
			||||||
 | 
					        email: string;
 | 
				
			||||||
 | 
					        password: string;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    interface Chainable {
 | 
				
			||||||
 | 
					        runBefore(): Chainable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        login_UI(user = AUTH_USER, password = AUTH_PASSWORD): Chainable;
 | 
				
			||||||
 | 
					        logout_UI(): Chainable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        createProject_UI(
 | 
				
			||||||
 | 
					            projectName: string,
 | 
				
			||||||
 | 
					            defaultStickiness: string
 | 
				
			||||||
 | 
					        ): Chainable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        createFeature_UI(
 | 
				
			||||||
 | 
					            name: string,
 | 
				
			||||||
 | 
					            shouldWait?: boolean,
 | 
				
			||||||
 | 
					            project?: string
 | 
				
			||||||
 | 
					        ): Chainable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // VARIANTS
 | 
				
			||||||
 | 
					        addVariantsToFeature_UI(
 | 
				
			||||||
 | 
					            featureToggleName: string,
 | 
				
			||||||
 | 
					            variants: Array<string>,
 | 
				
			||||||
 | 
					            projectName?: string
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        deleteVariant_UI(
 | 
				
			||||||
 | 
					            featureToggleName: string,
 | 
				
			||||||
 | 
					            variant: string,
 | 
				
			||||||
 | 
					            projectName?: string
 | 
				
			||||||
 | 
					        ): Chainable<any>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // SEGMENTS
 | 
				
			||||||
 | 
					        createSegment_UI(segmentName: string): Chainable;
 | 
				
			||||||
 | 
					        deleteSegment_UI(segmentName: string, id: string): Chainable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // STRATEGY
 | 
				
			||||||
 | 
					        addUserIdStrategyToFeature_UI(
 | 
				
			||||||
 | 
					            featureName: string,
 | 
				
			||||||
 | 
					            strategyId: string,
 | 
				
			||||||
 | 
					            projectName?: string
 | 
				
			||||||
 | 
					        ): Chainable;
 | 
				
			||||||
 | 
					        addFlexibleRolloutStrategyToFeature_UI(
 | 
				
			||||||
 | 
					            options: AddFlexibleRolloutStrategyOptions
 | 
				
			||||||
 | 
					        ): Chainable;
 | 
				
			||||||
 | 
					        updateFlexibleRolloutStrategy_UI(
 | 
				
			||||||
 | 
					            featureToggleName: string,
 | 
				
			||||||
 | 
					            strategyId: string
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        deleteFeatureStrategy_UI(
 | 
				
			||||||
 | 
					            featureName: string,
 | 
				
			||||||
 | 
					            strategyId: string,
 | 
				
			||||||
 | 
					            shouldWait?: boolean,
 | 
				
			||||||
 | 
					            projectName?: string
 | 
				
			||||||
 | 
					        ): Chainable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // API
 | 
				
			||||||
 | 
					        createUser_API(userName: string, role: number): Chainable;
 | 
				
			||||||
 | 
					        updateUserPassword_API(id: number, pass?: string): Chainable;
 | 
				
			||||||
 | 
					        addUserToProject_API(
 | 
				
			||||||
 | 
					            id: number,
 | 
				
			||||||
 | 
					            role: number,
 | 
				
			||||||
 | 
					            projectName?: string
 | 
				
			||||||
 | 
					        ): Chainable;
 | 
				
			||||||
 | 
					        createProject_API(name: string): Chainable;
 | 
				
			||||||
 | 
					        deleteProject_API(name: string): Chainable;
 | 
				
			||||||
 | 
					        createFeature_API(name: string, projectName: string): Chainable;
 | 
				
			||||||
 | 
					        deleteFeature_API(name: string): Chainable;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,269 +1,76 @@
 | 
				
			|||||||
/// <reference types="cypress" />
 | 
					///<reference path="../../global.d.ts" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ENTERPRISE = Boolean(Cypress.env('ENTERPRISE'));
 | 
					const ENTERPRISE = Boolean(Cypress.env('ENTERPRISE'));
 | 
				
			||||||
const randomId = String(Math.random()).split('.')[1];
 | 
					const randomId = String(Math.random()).split('.')[1];
 | 
				
			||||||
const featureToggleName = `unleash-e2e-${randomId}`;
 | 
					const featureToggleName = `unleash-e2e-${randomId}`;
 | 
				
			||||||
const baseUrl = Cypress.config().baseUrl;
 | 
					
 | 
				
			||||||
const variant1 = 'variant1';
 | 
					const variant1 = 'variant1';
 | 
				
			||||||
const variant2 = 'variant2';
 | 
					const variant2 = 'variant2';
 | 
				
			||||||
let strategyId = '';
 | 
					let strategyId = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Disable the prod guard modal by marking it as seen.
 | 
					 | 
				
			||||||
const disableFeatureStrategiesProdGuard = () => {
 | 
					 | 
				
			||||||
    localStorage.setItem(
 | 
					 | 
				
			||||||
        'useFeatureStrategyProdGuardSettings:v2',
 | 
					 | 
				
			||||||
        JSON.stringify({ hide: true })
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Disable all active splash pages by visiting them.
 | 
					 | 
				
			||||||
const disableActiveSplashScreens = () => {
 | 
					 | 
				
			||||||
    cy.visit(`/splash/operators`);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('feature', () => {
 | 
					describe('feature', () => {
 | 
				
			||||||
    before(() => {
 | 
					    before(() => {
 | 
				
			||||||
        disableFeatureStrategiesProdGuard();
 | 
					        cy.runBefore();
 | 
				
			||||||
        disableActiveSplashScreens();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    after(() => {
 | 
					    after(() => {
 | 
				
			||||||
        cy.request({
 | 
					        cy.deleteFeature_API(featureToggleName);
 | 
				
			||||||
            method: 'DELETE',
 | 
					 | 
				
			||||||
            url: `${baseUrl}/api/admin/features/${featureToggleName}`,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        cy.request({
 | 
					 | 
				
			||||||
            method: 'DELETE',
 | 
					 | 
				
			||||||
            url: `${baseUrl}/api/admin/archive/${featureToggleName}`,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    beforeEach(() => {
 | 
					    beforeEach(() => {
 | 
				
			||||||
        cy.login();
 | 
					        cy.login_UI();
 | 
				
			||||||
        cy.visit('/features');
 | 
					        cy.visit('/features');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('can create a feature toggle', () => {
 | 
					    it('can create a feature toggle', () => {
 | 
				
			||||||
        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
					        cy.createFeature_UI(featureToggleName, true);
 | 
				
			||||||
            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get('[data-testid=NAVIGATE_TO_CREATE_FEATURE').click();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.intercept('POST', '/api/admin/projects/default/features').as(
 | 
					 | 
				
			||||||
            'createFeature'
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_NAME_ID'").type(featureToggleName);
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_DESC_ID'").type('hello-world');
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_CREATE_BTN_ID']").click();
 | 
					 | 
				
			||||||
        cy.wait('@createFeature');
 | 
					 | 
				
			||||||
        cy.url().should('include', featureToggleName);
 | 
					        cy.url().should('include', featureToggleName);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('gives an error if a toggle exists with the same name', () => {
 | 
					    it('gives an error if a toggle exists with the same name', () => {
 | 
				
			||||||
        cy.get('[data-testid=NAVIGATE_TO_CREATE_FEATURE').click();
 | 
					        cy.createFeature_UI(featureToggleName, false);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.intercept('POST', '/api/admin/projects/default/features').as(
 | 
					 | 
				
			||||||
            'createFeature'
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_NAME_ID'").type(featureToggleName);
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_DESC_ID'").type('hello-world');
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_CREATE_BTN_ID']").click();
 | 
					 | 
				
			||||||
        cy.get("[data-testid='INPUT_ERROR_TEXT']").contains(
 | 
					        cy.get("[data-testid='INPUT_ERROR_TEXT']").contains(
 | 
				
			||||||
            'A toggle with that name already exists'
 | 
					            'A toggle with that name already exists'
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('gives an error if a toggle name is url unsafe', () => {
 | 
					    it('gives an error if a toggle name is url unsafe', () => {
 | 
				
			||||||
        cy.get('[data-testid=NAVIGATE_TO_CREATE_FEATURE').click();
 | 
					        cy.createFeature_UI('featureToggleUnsafe####$#//', false);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.intercept('POST', '/api/admin/projects/default/features').as(
 | 
					 | 
				
			||||||
            'createFeature'
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_NAME_ID'").type('featureToggleUnsafe####$#//');
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_DESC_ID'").type('hello-world');
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_CREATE_BTN_ID']").click();
 | 
					 | 
				
			||||||
        cy.get("[data-testid='INPUT_ERROR_TEXT']").contains(
 | 
					        cy.get("[data-testid='INPUT_ERROR_TEXT']").contains(
 | 
				
			||||||
            `"name" must be URL friendly`
 | 
					            `"name" must be URL friendly`
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('can add a gradual rollout strategy to the development environment', () => {
 | 
					    it('can add, update and delete a gradual rollout strategy to the development environment', async () => {
 | 
				
			||||||
        cy.visit(
 | 
					        cy.addFlexibleRolloutStrategyToFeature_UI({
 | 
				
			||||||
            `/projects/default/features/${featureToggleName}/strategies/create?environmentId=development&strategyName=flexibleRollout`
 | 
					            featureToggleName,
 | 
				
			||||||
 | 
					        }).then(value => {
 | 
				
			||||||
 | 
					            strategyId = value;
 | 
				
			||||||
 | 
					            cy.updateFlexibleRolloutStrategy_UI(
 | 
				
			||||||
 | 
					                featureToggleName,
 | 
				
			||||||
 | 
					                strategyId
 | 
				
			||||||
 | 
					            ).then(() =>
 | 
				
			||||||
 | 
					                cy.deleteFeatureStrategy_UI(featureToggleName, strategyId)
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (ENTERPRISE) {
 | 
					 | 
				
			||||||
            cy.get('[data-testid=ADD_CONSTRAINT_ID]').click();
 | 
					 | 
				
			||||||
            cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.intercept(
 | 
					 | 
				
			||||||
            'POST',
 | 
					 | 
				
			||||||
            `/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies`,
 | 
					 | 
				
			||||||
            req => {
 | 
					 | 
				
			||||||
                expect(req.body.name).to.equal('flexibleRollout');
 | 
					 | 
				
			||||||
                expect(req.body.parameters.groupId).to.equal(featureToggleName);
 | 
					 | 
				
			||||||
                expect(req.body.parameters.stickiness).to.equal('default');
 | 
					 | 
				
			||||||
                expect(req.body.parameters.rollout).to.equal('50');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (ENTERPRISE) {
 | 
					 | 
				
			||||||
                    expect(req.body.constraints.length).to.equal(1);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    expect(req.body.constraints.length).to.equal(0);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                req.continue(res => {
 | 
					 | 
				
			||||||
                    strategyId = res.body.id;
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        ).as('addStrategyToFeature');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get(`[data-testid=STRATEGY_FORM_SUBMIT_ID]`).first().click();
 | 
					 | 
				
			||||||
        cy.wait('@addStrategyToFeature');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('can update a strategy in the development environment', () => {
 | 
					 | 
				
			||||||
        cy.visit(
 | 
					 | 
				
			||||||
            `/projects/default/features/${featureToggleName}/strategies/edit?environmentId=development&strategyId=${strategyId}`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get('[data-testid=FLEXIBLE_STRATEGY_STICKINESS_ID]')
 | 
					 | 
				
			||||||
            .first()
 | 
					 | 
				
			||||||
            .click()
 | 
					 | 
				
			||||||
            .get('[data-testid=SELECT_ITEM_ID-sessionId')
 | 
					 | 
				
			||||||
            .first()
 | 
					 | 
				
			||||||
            .click();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get('[data-testid=FLEXIBLE_STRATEGY_GROUP_ID]')
 | 
					 | 
				
			||||||
            .first()
 | 
					 | 
				
			||||||
            .clear()
 | 
					 | 
				
			||||||
            .type('new-group-id');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.intercept(
 | 
					 | 
				
			||||||
            'PUT',
 | 
					 | 
				
			||||||
            `/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies/${strategyId}`,
 | 
					 | 
				
			||||||
            req => {
 | 
					 | 
				
			||||||
                expect(req.body.parameters.groupId).to.equal('new-group-id');
 | 
					 | 
				
			||||||
                expect(req.body.parameters.stickiness).to.equal('sessionId');
 | 
					 | 
				
			||||||
                expect(req.body.parameters.rollout).to.equal('50');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (ENTERPRISE) {
 | 
					 | 
				
			||||||
                    expect(req.body.constraints.length).to.equal(1);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    expect(req.body.constraints.length).to.equal(0);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                req.continue(res => {
 | 
					 | 
				
			||||||
                    expect(res.statusCode).to.equal(200);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        ).as('updateStrategy');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get(`[data-testid=STRATEGY_FORM_SUBMIT_ID]`).first().click();
 | 
					 | 
				
			||||||
        cy.wait('@updateStrategy');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('can delete a strategy in the development environment', () => {
 | 
					 | 
				
			||||||
        cy.visit(`/projects/default/features/${featureToggleName}`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.intercept(
 | 
					 | 
				
			||||||
            'DELETE',
 | 
					 | 
				
			||||||
            `/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies/${strategyId}`,
 | 
					 | 
				
			||||||
            req => {
 | 
					 | 
				
			||||||
                req.continue(res => {
 | 
					 | 
				
			||||||
                    expect(res.statusCode).to.equal(200);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        ).as('deleteStrategy');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get(
 | 
					 | 
				
			||||||
            '[data-testid=FEATURE_ENVIRONMENT_ACCORDION_development]'
 | 
					 | 
				
			||||||
        ).click();
 | 
					 | 
				
			||||||
        cy.get('[data-testid=STRATEGY_FORM_REMOVE_ID]').click();
 | 
					 | 
				
			||||||
        cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
					 | 
				
			||||||
        cy.wait('@deleteStrategy');
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('can add a userId strategy to the development environment', () => {
 | 
					    it('can add a userId strategy to the development environment', () => {
 | 
				
			||||||
        cy.visit(
 | 
					        cy.addUserIdStrategyToFeature_UI(featureToggleName, strategyId).then(
 | 
				
			||||||
            `/projects/default/features/${featureToggleName}/strategies/create?environmentId=development&strategyName=userWithId`
 | 
					            value => {
 | 
				
			||||||
 | 
					                cy.deleteFeatureStrategy_UI(featureToggleName, value, false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (ENTERPRISE) {
 | 
					 | 
				
			||||||
            cy.get('[data-testid=ADD_CONSTRAINT_ID]').click();
 | 
					 | 
				
			||||||
            cy.get('[data-testid=CONSTRAINT_AUTOCOMPLETE_ID]')
 | 
					 | 
				
			||||||
                .type('{downArrow}'.repeat(1))
 | 
					 | 
				
			||||||
                .type('{enter}');
 | 
					 | 
				
			||||||
            cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get('[data-testid=STRATEGY_INPUT_LIST]')
 | 
					 | 
				
			||||||
            .type('user1')
 | 
					 | 
				
			||||||
            .type('{enter}')
 | 
					 | 
				
			||||||
            .type('user2')
 | 
					 | 
				
			||||||
            .type('{enter}');
 | 
					 | 
				
			||||||
        cy.get('[data-testid=ADD_TO_STRATEGY_INPUT_LIST]').click();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.intercept(
 | 
					 | 
				
			||||||
            'POST',
 | 
					 | 
				
			||||||
            `/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies`,
 | 
					 | 
				
			||||||
            req => {
 | 
					 | 
				
			||||||
                expect(req.body.name).to.equal('userWithId');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                expect(req.body.parameters.userIds.length).to.equal(11);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (ENTERPRISE) {
 | 
					 | 
				
			||||||
                    expect(req.body.constraints.length).to.equal(1);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    expect(req.body.constraints.length).to.equal(0);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                req.continue(res => {
 | 
					 | 
				
			||||||
                    strategyId = res.body.id;
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        ).as('addStrategyToFeature');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get(`[data-testid=STRATEGY_FORM_SUBMIT_ID]`).first().click();
 | 
					 | 
				
			||||||
        cy.wait('@addStrategyToFeature');
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('can add two variants to the development environment', () => {
 | 
					    it('can add variants to the development environment', () => {
 | 
				
			||||||
        cy.visit(`/projects/default/features/${featureToggleName}/variants`);
 | 
					        cy.addVariantsToFeature_UI(featureToggleName, [variant1, variant2]);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.intercept(
 | 
					 | 
				
			||||||
            'PATCH',
 | 
					 | 
				
			||||||
            `/api/admin/projects/default/features/${featureToggleName}/environments/development/variants`,
 | 
					 | 
				
			||||||
            req => {
 | 
					 | 
				
			||||||
                expect(req.body[0].op).to.equal('add');
 | 
					 | 
				
			||||||
                expect(req.body[0].path).to.equal('/0');
 | 
					 | 
				
			||||||
                expect(req.body[0].value.name).to.equal(variant1);
 | 
					 | 
				
			||||||
                expect(req.body[0].value.weight).to.equal(500);
 | 
					 | 
				
			||||||
                expect(req.body[1].op).to.equal('add');
 | 
					 | 
				
			||||||
                expect(req.body[1].path).to.equal('/1');
 | 
					 | 
				
			||||||
                expect(req.body[1].value.name).to.equal(variant2);
 | 
					 | 
				
			||||||
                expect(req.body[1].value.weight).to.equal(500);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        ).as('variantCreation');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get('[data-testid=ADD_VARIANT_BUTTON]').first().click();
 | 
					 | 
				
			||||||
        cy.wait(1000);
 | 
					 | 
				
			||||||
        cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variant1);
 | 
					 | 
				
			||||||
        cy.get('[data-testid=MODAL_ADD_VARIANT_BUTTON]').click();
 | 
					 | 
				
			||||||
        cy.get('[data-testid=VARIANT_NAME_INPUT]').last().type(variant2);
 | 
					 | 
				
			||||||
        cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
					 | 
				
			||||||
        cy.wait('@variantCreation');
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('can set weight to fixed value for one of the variants', () => {
 | 
					    it('can update variants', () => {
 | 
				
			||||||
        cy.visit(`/projects/default/features/${featureToggleName}/variants`);
 | 
					        cy.visit(`/projects/default/features/${featureToggleName}/variants`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cy.get('[data-testid=EDIT_VARIANTS_BUTTON]').click();
 | 
					        cy.get('[data-testid=EDIT_VARIANTS_BUTTON]').click();
 | 
				
			||||||
        cy.wait(1000);
 | 
					 | 
				
			||||||
        cy.get('[data-testid=VARIANT_NAME_INPUT]')
 | 
					        cy.get('[data-testid=VARIANT_NAME_INPUT]')
 | 
				
			||||||
            .last()
 | 
					            .last()
 | 
				
			||||||
            .children()
 | 
					            .children()
 | 
				
			||||||
@ -292,32 +99,13 @@ describe('feature', () => {
 | 
				
			|||||||
        ).as('variantUpdate');
 | 
					        ).as('variantUpdate');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
					        cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
				
			||||||
        cy.wait('@variantUpdate');
 | 
					 | 
				
			||||||
        cy.get(`[data-testid=VARIANT_WEIGHT_${variant2}]`).should(
 | 
					        cy.get(`[data-testid=VARIANT_WEIGHT_${variant2}]`).should(
 | 
				
			||||||
            'have.text',
 | 
					            'have.text',
 | 
				
			||||||
            '15 %'
 | 
					            '15 %'
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('can delete variant', () => {
 | 
					    it('can delete variants', () => {
 | 
				
			||||||
        cy.visit(`/projects/default/features/${featureToggleName}/variants`);
 | 
					        cy.deleteVariant_UI(featureToggleName, variant2);
 | 
				
			||||||
        cy.get('[data-testid=EDIT_VARIANTS_BUTTON]').click();
 | 
					 | 
				
			||||||
        cy.wait(1000);
 | 
					 | 
				
			||||||
        cy.get(`[data-testid=VARIANT_DELETE_BUTTON_${variant2}]`).click();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.intercept(
 | 
					 | 
				
			||||||
            'PATCH',
 | 
					 | 
				
			||||||
            `/api/admin/projects/default/features/${featureToggleName}/environments/development/variants`,
 | 
					 | 
				
			||||||
            req => {
 | 
					 | 
				
			||||||
                expect(req.body[0].op).to.equal('remove');
 | 
					 | 
				
			||||||
                expect(req.body[0].path).to.equal('/1');
 | 
					 | 
				
			||||||
                expect(req.body[1].op).to.equal('replace');
 | 
					 | 
				
			||||||
                expect(req.body[1].path).to.equal('/0/weight');
 | 
					 | 
				
			||||||
                expect(req.body[1].value).to.equal(1000);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        ).as('delete');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
					 | 
				
			||||||
        cy.wait('@delete');
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
/// <reference types="cypress" />
 | 
					///<reference path="../../global.d.ts" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const baseUrl = Cypress.config().baseUrl;
 | 
					const baseUrl = Cypress.config().baseUrl;
 | 
				
			||||||
const randomId = String(Math.random()).split('.')[1];
 | 
					const randomId = String(Math.random()).split('.')[1];
 | 
				
			||||||
@ -12,8 +12,8 @@ const disableActiveSplashScreens = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
describe('groups', () => {
 | 
					describe('groups', () => {
 | 
				
			||||||
    before(() => {
 | 
					    before(() => {
 | 
				
			||||||
        disableActiveSplashScreens();
 | 
					        cy.runBefore();
 | 
				
			||||||
        cy.login();
 | 
					        cy.login_UI();
 | 
				
			||||||
        for (let i = 1; i <= 2; i++) {
 | 
					        for (let i = 1; i <= 2; i++) {
 | 
				
			||||||
            cy.request('POST', `${baseUrl}/api/admin/user-admin`, {
 | 
					            cy.request('POST', `${baseUrl}/api/admin/user-admin`, {
 | 
				
			||||||
                name: `unleash-e2e-user${i}-${randomId}`,
 | 
					                name: `unleash-e2e-user${i}-${randomId}`,
 | 
				
			||||||
@ -31,7 +31,7 @@ describe('groups', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    beforeEach(() => {
 | 
					    beforeEach(() => {
 | 
				
			||||||
        cy.login();
 | 
					        cy.login_UI();
 | 
				
			||||||
        cy.visit('/admin/groups');
 | 
					        cy.visit('/admin/groups');
 | 
				
			||||||
        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
					        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
				
			||||||
            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
					            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
/// <reference types="cypress" />
 | 
					///<reference path="../../global.d.ts" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const baseUrl = Cypress.config().baseUrl;
 | 
					const baseUrl = Cypress.config().baseUrl;
 | 
				
			||||||
const randomSeed = String(Math.random()).split('.')[1];
 | 
					const randomSeed = String(Math.random()).split('.')[1];
 | 
				
			||||||
@ -13,7 +13,7 @@ const disableActiveSplashScreens = () => {
 | 
				
			|||||||
describe('imports', () => {
 | 
					describe('imports', () => {
 | 
				
			||||||
    before(() => {
 | 
					    before(() => {
 | 
				
			||||||
        disableActiveSplashScreens();
 | 
					        disableActiveSplashScreens();
 | 
				
			||||||
        cy.login();
 | 
					        cy.login_UI();
 | 
				
			||||||
        for (let i = 1; i <= 2; i++) {
 | 
					        for (let i = 1; i <= 2; i++) {
 | 
				
			||||||
            cy.request('POST', `${baseUrl}/api/admin/user-admin`, {
 | 
					            cy.request('POST', `${baseUrl}/api/admin/user-admin`, {
 | 
				
			||||||
                name: `unleash-e2e-user${i}-${randomFeatureName}`,
 | 
					                name: `unleash-e2e-user${i}-${randomFeatureName}`,
 | 
				
			||||||
@ -31,7 +31,7 @@ describe('imports', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    beforeEach(() => {
 | 
					    beforeEach(() => {
 | 
				
			||||||
        cy.login();
 | 
					        cy.login_UI();
 | 
				
			||||||
        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
					        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
				
			||||||
            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
					            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
/// <reference types="cypress" />
 | 
					///<reference path="../../global.d.ts" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    PA_ASSIGN_BUTTON_ID,
 | 
					    PA_ASSIGN_BUTTON_ID,
 | 
				
			||||||
@ -8,6 +8,7 @@ import {
 | 
				
			|||||||
    PA_ROLE_ID,
 | 
					    PA_ROLE_ID,
 | 
				
			||||||
    PA_USERS_GROUPS_ID,
 | 
					    PA_USERS_GROUPS_ID,
 | 
				
			||||||
    PA_USERS_GROUPS_TITLE_ID,
 | 
					    PA_USERS_GROUPS_TITLE_ID,
 | 
				
			||||||
 | 
					    //@ts-ignore
 | 
				
			||||||
} from '../../../src/utils/testIds';
 | 
					} from '../../../src/utils/testIds';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const baseUrl = Cypress.config().baseUrl;
 | 
					const baseUrl = Cypress.config().baseUrl;
 | 
				
			||||||
@ -25,7 +26,7 @@ const disableActiveSplashScreens = () => {
 | 
				
			|||||||
describe('project-access', () => {
 | 
					describe('project-access', () => {
 | 
				
			||||||
    before(() => {
 | 
					    before(() => {
 | 
				
			||||||
        disableActiveSplashScreens();
 | 
					        disableActiveSplashScreens();
 | 
				
			||||||
        cy.login();
 | 
					        cy.login_UI();
 | 
				
			||||||
        for (let i = 1; i <= 2; i++) {
 | 
					        for (let i = 1; i <= 2; i++) {
 | 
				
			||||||
            const name = `${i}-${userName}`;
 | 
					            const name = `${i}-${userName}`;
 | 
				
			||||||
            cy.request('POST', `${baseUrl}/api/admin/user-admin`, {
 | 
					            cy.request('POST', `${baseUrl}/api/admin/user-admin`, {
 | 
				
			||||||
@ -68,7 +69,7 @@ describe('project-access', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    beforeEach(() => {
 | 
					    beforeEach(() => {
 | 
				
			||||||
        cy.login();
 | 
					        cy.login_UI();
 | 
				
			||||||
        cy.visit(`/projects/${groupAndProjectName}/settings/access`);
 | 
					        cy.visit(`/projects/${groupAndProjectName}/settings/access`);
 | 
				
			||||||
        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
					        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
				
			||||||
            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
					            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,112 +1,34 @@
 | 
				
			|||||||
/// <reference types="cypress" />
 | 
					///<reference path="../../global.d.ts" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import UserCredentials = Cypress.UserCredentials;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type UserCredentials = { email: string; password: string };
 | 
					 | 
				
			||||||
const ENTERPRISE = Boolean(Cypress.env('ENTERPRISE'));
 | 
					const ENTERPRISE = Boolean(Cypress.env('ENTERPRISE'));
 | 
				
			||||||
const randomId = String(Math.random()).split('.')[1];
 | 
					const randomId = String(Math.random()).split('.')[1];
 | 
				
			||||||
const featureToggleName = `notifications_test-${randomId}`;
 | 
					const featureToggleName = `notifications_test-${randomId}`;
 | 
				
			||||||
const baseUrl = Cypress.config().baseUrl;
 | 
					const baseUrl = Cypress.config().baseUrl;
 | 
				
			||||||
let strategyId = '';
 | 
					let strategyId = '';
 | 
				
			||||||
const userIds: number[] = [];
 | 
					let userIds: number[] = [];
 | 
				
			||||||
const userCredentials: UserCredentials[] = [];
 | 
					let userCredentials: UserCredentials[] = [];
 | 
				
			||||||
const userName = `notifications_user-${randomId}`;
 | 
					const userName = `notifications_user-${randomId}`;
 | 
				
			||||||
const projectName = `default`;
 | 
					const projectName = `default`;
 | 
				
			||||||
const password = Cypress.env(`AUTH_PASSWORD`) + '_A';
 | 
					
 | 
				
			||||||
const EDITOR = 2;
 | 
					const EDITOR = 2;
 | 
				
			||||||
const PROJECT_MEMBER = 5;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Disable all active splash pages by visiting them.
 | 
					 | 
				
			||||||
const disableActiveSplashScreens = () => {
 | 
					 | 
				
			||||||
    cy.visit(`/splash/operators`);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const createUser = () => {
 | 
					 | 
				
			||||||
    const name = `${userName}`;
 | 
					 | 
				
			||||||
    const email = `${name}@test.com`;
 | 
					 | 
				
			||||||
    cy.request('POST', `${baseUrl}/api/admin/user-admin`, {
 | 
					 | 
				
			||||||
        name: name,
 | 
					 | 
				
			||||||
        email: `${name}@test.com`,
 | 
					 | 
				
			||||||
        username: `${name}@test.com`,
 | 
					 | 
				
			||||||
        sendEmail: false,
 | 
					 | 
				
			||||||
        rootRole: EDITOR,
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
        .as(name)
 | 
					 | 
				
			||||||
        .then(response => {
 | 
					 | 
				
			||||||
            const id = response.body.id;
 | 
					 | 
				
			||||||
            updateUserPassword(id).then(() => {
 | 
					 | 
				
			||||||
                addUserToProject(id).then(() => {
 | 
					 | 
				
			||||||
                    userIds.push(id);
 | 
					 | 
				
			||||||
                    userCredentials.push({ email, password });
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const updateUserPassword = (id: number) =>
 | 
					 | 
				
			||||||
    cy.request(
 | 
					 | 
				
			||||||
        'POST',
 | 
					 | 
				
			||||||
        `${baseUrl}/api/admin/user-admin/${id}/change-password`,
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            password,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const addUserToProject = (id: number) =>
 | 
					 | 
				
			||||||
    cy.request(
 | 
					 | 
				
			||||||
        'POST',
 | 
					 | 
				
			||||||
        `${baseUrl}/api/admin/projects/${projectName}/role/${PROJECT_MEMBER}/access`,
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            groups: [],
 | 
					 | 
				
			||||||
            users: [{ id }],
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('notifications', () => {
 | 
					describe('notifications', () => {
 | 
				
			||||||
    before(() => {
 | 
					    before(() => {
 | 
				
			||||||
        disableActiveSplashScreens();
 | 
					        cy.runBefore();
 | 
				
			||||||
        cy.login();
 | 
					 | 
				
			||||||
        createUser();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    after(() => {
 | 
					 | 
				
			||||||
        // We need to login as admin for cleanup
 | 
					 | 
				
			||||||
        cy.login();
 | 
					 | 
				
			||||||
        userIds.forEach(id =>
 | 
					 | 
				
			||||||
            cy.request('DELETE', `${baseUrl}/api/admin/user-admin/${id}`)
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.request(
 | 
					 | 
				
			||||||
            'DELETE',
 | 
					 | 
				
			||||||
            `${baseUrl}/api/admin/features/${featureToggleName}`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    beforeEach(() => {
 | 
					 | 
				
			||||||
        cy.login();
 | 
					 | 
				
			||||||
        cy.visit(`/projects/${projectName}`);
 | 
					 | 
				
			||||||
        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
					 | 
				
			||||||
            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    afterEach(() => {
 | 
					 | 
				
			||||||
        cy.logout();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const createFeature = () => {
 | 
					 | 
				
			||||||
        cy.get('[data-testid=NAVIGATE_TO_CREATE_FEATURE').click();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.intercept('POST', `/api/admin/projects/${projectName}/features`).as(
 | 
					 | 
				
			||||||
            'createFeature'
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_NAME_ID'").type(featureToggleName);
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_DESC_ID'").type('hello-world');
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_CREATE_BTN_ID']").click();
 | 
					 | 
				
			||||||
        cy.wait('@createFeature');
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should create a notification when a feature is created in a project', () => {
 | 
					    it('should create a notification when a feature is created in a project', () => {
 | 
				
			||||||
        createFeature();
 | 
					        cy.login_UI();
 | 
				
			||||||
 | 
					        cy.createUser_API(userName, EDITOR).then(value => {
 | 
				
			||||||
 | 
					            userIds = value.userIds;
 | 
				
			||||||
 | 
					            userCredentials = value.userCredentials;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cy.login_UI();
 | 
				
			||||||
 | 
					            cy.visit(`/projects/${projectName}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cy.createFeature_UI(featureToggleName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            //Should not show own notifications
 | 
					            //Should not show own notifications
 | 
				
			||||||
            cy.get("[data-testid='NOTIFICATIONS_BUTTON']").click();
 | 
					            cy.get("[data-testid='NOTIFICATIONS_BUTTON']").click();
 | 
				
			||||||
@ -117,18 +39,24 @@ describe('notifications', () => {
 | 
				
			|||||||
            const credentials = userCredentials[0];
 | 
					            const credentials = userCredentials[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            //Sign in as a different user
 | 
					            //Sign in as a different user
 | 
				
			||||||
        cy.login(credentials.email, credentials.password);
 | 
					            cy.login_UI(credentials.email, credentials.password);
 | 
				
			||||||
            cy.visit(`/projects/${projectName}`);
 | 
					            cy.visit(`/projects/${projectName}`);
 | 
				
			||||||
        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
					 | 
				
			||||||
            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
            cy.get("[data-testid='NOTIFICATIONS_BUTTON']").click();
 | 
					            cy.get("[data-testid='NOTIFICATIONS_BUTTON']").click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            //then
 | 
					            //then
 | 
				
			||||||
            cy.get("[data-testid='UNREAD_NOTIFICATIONS']").should('exist');
 | 
					            cy.get("[data-testid='UNREAD_NOTIFICATIONS']").should('exist');
 | 
				
			||||||
            cy.get("[data-testid='NOTIFICATIONS_LIST']")
 | 
					            cy.get("[data-testid='NOTIFICATIONS_LIST']")
 | 
				
			||||||
            .should('have.length', 1)
 | 
					 | 
				
			||||||
                .eq(0)
 | 
					                .eq(0)
 | 
				
			||||||
            .should('contain.text', 'New feature');
 | 
					                .should('contain.text', `New feature ${featureToggleName}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //clean
 | 
				
			||||||
 | 
					            // We need to login as admin for cleanup
 | 
				
			||||||
 | 
					            cy.login_UI();
 | 
				
			||||||
 | 
					            userIds.forEach(id =>
 | 
				
			||||||
 | 
					                cy.request('DELETE', `${baseUrl}/api/admin/user-admin/${id}`)
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cy.deleteFeature_API(featureToggleName);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,11 @@
 | 
				
			|||||||
/// <reference types="cypress" />
 | 
					///<reference path="../../global.d.ts" />
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    BATCH_ACTIONS_BAR,
 | 
					    BATCH_ACTIONS_BAR,
 | 
				
			||||||
    BATCH_SELECT,
 | 
					    BATCH_SELECT,
 | 
				
			||||||
    BATCH_SELECTED_COUNT,
 | 
					    BATCH_SELECTED_COUNT,
 | 
				
			||||||
    MORE_BATCH_ACTIONS,
 | 
					    MORE_BATCH_ACTIONS,
 | 
				
			||||||
    SEARCH_INPUT,
 | 
					    SEARCH_INPUT,
 | 
				
			||||||
 | 
					    //@ts-ignore
 | 
				
			||||||
} from '../../../src/utils/testIds';
 | 
					} from '../../../src/utils/testIds';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const randomId = String(Math.random()).split('.')[1];
 | 
					const randomId = String(Math.random()).split('.')[1];
 | 
				
			||||||
@ -13,51 +14,9 @@ const featureToggleName = `${featureTogglePrefix}-${randomId}`;
 | 
				
			|||||||
const baseUrl = Cypress.config().baseUrl;
 | 
					const baseUrl = Cypress.config().baseUrl;
 | 
				
			||||||
const selectAll = '[title="Toggle All Rows Selected"] input[type="checkbox"]';
 | 
					const selectAll = '[title="Toggle All Rows Selected"] input[type="checkbox"]';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Disable the prod guard modal by marking it as seen.
 | 
					 | 
				
			||||||
const disableFeatureStrategiesProdGuard = () => {
 | 
					 | 
				
			||||||
    localStorage.setItem(
 | 
					 | 
				
			||||||
        'useFeatureStrategyProdGuardSettings:v2',
 | 
					 | 
				
			||||||
        JSON.stringify({ hide: true })
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Disable all active splash pages by visiting them.
 | 
					 | 
				
			||||||
const disableActiveSplashScreens = () => {
 | 
					 | 
				
			||||||
    cy.visit(`/splash/operators`);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('project overview', () => {
 | 
					describe('project overview', () => {
 | 
				
			||||||
    before(() => {
 | 
					    before(() => {
 | 
				
			||||||
        disableFeatureStrategiesProdGuard();
 | 
					        cy.runBefore();
 | 
				
			||||||
        disableActiveSplashScreens();
 | 
					 | 
				
			||||||
        cy.login();
 | 
					 | 
				
			||||||
        cy.request({
 | 
					 | 
				
			||||||
            url: '/api/admin/projects/default/features',
 | 
					 | 
				
			||||||
            method: 'POST',
 | 
					 | 
				
			||||||
            body: {
 | 
					 | 
				
			||||||
                name: `${featureToggleName}-A`,
 | 
					 | 
				
			||||||
                description: 'hello-world',
 | 
					 | 
				
			||||||
                type: 'release',
 | 
					 | 
				
			||||||
                impressionData: false,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        cy.request({
 | 
					 | 
				
			||||||
            url: '/api/admin/projects/default/features',
 | 
					 | 
				
			||||||
            method: 'POST',
 | 
					 | 
				
			||||||
            body: {
 | 
					 | 
				
			||||||
                name: `${featureToggleName}-B`,
 | 
					 | 
				
			||||||
                description: 'hello-world',
 | 
					 | 
				
			||||||
                type: 'release',
 | 
					 | 
				
			||||||
                impressionData: false,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    beforeEach(() => {
 | 
					 | 
				
			||||||
        cy.login();
 | 
					 | 
				
			||||||
        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
					 | 
				
			||||||
            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    after(() => {
 | 
					    after(() => {
 | 
				
			||||||
@ -82,6 +41,9 @@ describe('project overview', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('loads the table', () => {
 | 
					    it('loads the table', () => {
 | 
				
			||||||
 | 
					        cy.login_UI();
 | 
				
			||||||
 | 
					        cy.createFeature_API(`${featureToggleName}-A`);
 | 
				
			||||||
 | 
					        cy.createFeature_API(`${featureToggleName}-B`);
 | 
				
			||||||
        cy.visit('/projects/default');
 | 
					        cy.visit('/projects/default');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Use search to filter feature toggles and check that the feature toggle is listed in the table.
 | 
					        // Use search to filter feature toggles and check that the feature toggle is listed in the table.
 | 
				
			||||||
@ -91,6 +53,7 @@ describe('project overview', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('can select and deselect feature toggles', () => {
 | 
					    it('can select and deselect feature toggles', () => {
 | 
				
			||||||
 | 
					        cy.login_UI();
 | 
				
			||||||
        cy.visit('/projects/default');
 | 
					        cy.visit('/projects/default');
 | 
				
			||||||
        cy.viewport(1920, 1080);
 | 
					        cy.viewport(1920, 1080);
 | 
				
			||||||
        cy.get("[data-testid='SEARCH_INPUT']").click().type(featureToggleName);
 | 
					        cy.get("[data-testid='SEARCH_INPUT']").click().type(featureToggleName);
 | 
				
			||||||
@ -138,9 +101,12 @@ describe('project overview', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('can mark selected togggles as stale', () => {
 | 
					    it('can mark selected togggles as stale', () => {
 | 
				
			||||||
 | 
					        cy.login_UI();
 | 
				
			||||||
        cy.visit('/projects/default');
 | 
					        cy.visit('/projects/default');
 | 
				
			||||||
        cy.viewport(1920, 1080);
 | 
					        cy.viewport(1920, 1080);
 | 
				
			||||||
        cy.get(`[data-testid='${SEARCH_INPUT}']`).click().type(featureToggleName);
 | 
					        cy.get(`[data-testid='${SEARCH_INPUT}']`)
 | 
				
			||||||
 | 
					            .click()
 | 
				
			||||||
 | 
					            .type(featureToggleName);
 | 
				
			||||||
        cy.get('table tbody tr').should('have.length', 2);
 | 
					        cy.get('table tbody tr').should('have.length', 2);
 | 
				
			||||||
        cy.get(selectAll).click();
 | 
					        cy.get(selectAll).click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -153,9 +119,12 @@ describe('project overview', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('can archive selected togggles', () => {
 | 
					    it('can archive selected togggles', () => {
 | 
				
			||||||
 | 
					        cy.login_UI();
 | 
				
			||||||
        cy.visit('/projects/default');
 | 
					        cy.visit('/projects/default');
 | 
				
			||||||
        cy.viewport(1920, 1080);
 | 
					        cy.viewport(1920, 1080);
 | 
				
			||||||
        cy.get(`[data-testid='${SEARCH_INPUT}']`).click().type(featureToggleName);
 | 
					        cy.get(`[data-testid='${SEARCH_INPUT}']`)
 | 
				
			||||||
 | 
					            .click()
 | 
				
			||||||
 | 
					            .type(featureToggleName);
 | 
				
			||||||
        cy.get('table tbody tr').should('have.length', 2);
 | 
					        cy.get('table tbody tr').should('have.length', 2);
 | 
				
			||||||
        cy.get(selectAll).click();
 | 
					        cy.get(selectAll).click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,117 +1,80 @@
 | 
				
			|||||||
/// <reference types="cypress" />
 | 
					///<reference path="../../global.d.ts" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type UserCredentials = { email: string; password: string };
 | 
					 | 
				
			||||||
const ENTERPRISE = Boolean(Cypress.env('ENTERPRISE'));
 | 
					 | 
				
			||||||
const randomId = String(Math.random()).split('.')[1];
 | 
					const randomId = String(Math.random()).split('.')[1];
 | 
				
			||||||
const featureToggleName = `settings-${randomId}`;
 | 
					 | 
				
			||||||
const baseUrl = Cypress.config().baseUrl;
 | 
					const baseUrl = Cypress.config().baseUrl;
 | 
				
			||||||
let strategyId = '';
 | 
					let strategyId = '';
 | 
				
			||||||
const userName = `settings-user-${randomId}`;
 | 
					const userName = `settings-user-${randomId}`;
 | 
				
			||||||
const projectName = `stickiness-project-${randomId}`;
 | 
					const projectName = `stickiness-project-${randomId}`;
 | 
				
			||||||
 | 
					const TEST_STICKINESS = 'userId';
 | 
				
			||||||
 | 
					const featureToggleName = `settings-${randomId}`;
 | 
				
			||||||
 | 
					let cleanFeature = false;
 | 
				
			||||||
 | 
					let cleanProject = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Disable all active splash pages by visiting them.
 | 
					describe('project settings', () => {
 | 
				
			||||||
const disableActiveSplashScreens = () => {
 | 
					 | 
				
			||||||
    cy.visit(`/splash/operators`);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const disableFeatureStrategiesProdGuard = () => {
 | 
					 | 
				
			||||||
    localStorage.setItem(
 | 
					 | 
				
			||||||
        'useFeatureStrategyProdGuardSettings:v2',
 | 
					 | 
				
			||||||
        JSON.stringify({ hide: true })
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('notifications', () => {
 | 
					 | 
				
			||||||
    before(() => {
 | 
					    before(() => {
 | 
				
			||||||
        disableFeatureStrategiesProdGuard();
 | 
					        cy.runBefore();
 | 
				
			||||||
        disableActiveSplashScreens();
 | 
					 | 
				
			||||||
        cy.login();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    after(() => {
 | 
					 | 
				
			||||||
        cy.request(
 | 
					 | 
				
			||||||
            'DELETE',
 | 
					 | 
				
			||||||
            `${baseUrl}/api/admin/features/${featureToggleName}`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.request('DELETE', `${baseUrl}/api/admin/projects/${projectName}`);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    beforeEach(() => {
 | 
					    beforeEach(() => {
 | 
				
			||||||
        cy.login();
 | 
					        cy.login_UI();
 | 
				
			||||||
        cy.visit(`/projects`);
 | 
					        if (cleanFeature) {
 | 
				
			||||||
        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
					            cy.deleteFeature_API(featureToggleName);
 | 
				
			||||||
            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (cleanProject) {
 | 
				
			||||||
 | 
					            cy.deleteProject_API(projectName);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        cy.visit(`/projects`);
 | 
				
			||||||
 | 
					        cy.wait(300);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    afterEach(() => {
 | 
					 | 
				
			||||||
        cy.request('DELETE', `${baseUrl}/api/admin/projects/${projectName}`);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const createFeature = () => {
 | 
					 | 
				
			||||||
        cy.get('[data-testid=NAVIGATE_TO_CREATE_FEATURE').click();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.intercept('POST', `/api/admin/projects/${projectName}/features`).as(
 | 
					 | 
				
			||||||
            'createFeature'
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_NAME_ID'").type(featureToggleName);
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_DESC_ID'").type('hello-world');
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CF_CREATE_BTN_ID']").click();
 | 
					 | 
				
			||||||
        cy.wait('@createFeature');
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const createProject = () => {
 | 
					 | 
				
			||||||
        cy.get('[data-testid=NAVIGATE_TO_CREATE_PROJECT').click();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get("[data-testid='PROJECT_ID_INPUT']").type(projectName);
 | 
					 | 
				
			||||||
        cy.get("[data-testid='PROJECT_NAME_INPUT']").type(projectName);
 | 
					 | 
				
			||||||
        cy.get("[id='stickiness-select']")
 | 
					 | 
				
			||||||
            .first()
 | 
					 | 
				
			||||||
            .click()
 | 
					 | 
				
			||||||
            .get('[data-testid=SELECT_ITEM_ID-userId')
 | 
					 | 
				
			||||||
            .first()
 | 
					 | 
				
			||||||
            .click();
 | 
					 | 
				
			||||||
        cy.get("[data-testid='CREATE_PROJECT_BTN']").click();
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('should store default project stickiness when creating, retrieve it when editing a project', () => {
 | 
					    it('should store default project stickiness when creating, retrieve it when editing a project', () => {
 | 
				
			||||||
        createProject();
 | 
					        //when
 | 
				
			||||||
 | 
					        cleanProject = true;
 | 
				
			||||||
 | 
					        cy.createProject_UI(projectName, TEST_STICKINESS);
 | 
				
			||||||
        cy.visit(`/projects/${projectName}`);
 | 
					        cy.visit(`/projects/${projectName}`);
 | 
				
			||||||
        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
					 | 
				
			||||||
            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        cy.get("[data-testid='NAVIGATE_TO_EDIT_PROJECT']").click();
 | 
					        cy.get("[data-testid='NAVIGATE_TO_EDIT_PROJECT']").click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //then
 | 
					        //then
 | 
				
			||||||
        cy.get("[id='stickiness-select']")
 | 
					        cy.get("[id='stickiness-select']")
 | 
				
			||||||
            .first()
 | 
					            .first()
 | 
				
			||||||
            .should('have.text', 'userId');
 | 
					            .should('have.text', 'userId');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //clean
 | 
				
			||||||
 | 
					        cy.request('DELETE', `${baseUrl}/api/admin/projects/${projectName}`);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('should respect the default project stickiness when creating a Gradual Rollout Strategy', () => {
 | 
					    it('should respect the default project stickiness when creating a Gradual Rollout Strategy', () => {
 | 
				
			||||||
        createProject();
 | 
					        cy.createProject_UI(projectName, TEST_STICKINESS);
 | 
				
			||||||
        createFeature();
 | 
					        cy.createFeature_UI(featureToggleName, true, projectName);
 | 
				
			||||||
 | 
					        cleanFeature = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //when - then
 | 
				
			||||||
 | 
					        cy.addFlexibleRolloutStrategyToFeature_UI({
 | 
				
			||||||
 | 
					            featureToggleName,
 | 
				
			||||||
 | 
					            project: projectName,
 | 
				
			||||||
 | 
					            stickiness: TEST_STICKINESS,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //clean
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should respect the default project stickiness when creating a variant', () => {
 | 
				
			||||||
 | 
					        cy.createProject_UI(projectName, TEST_STICKINESS);
 | 
				
			||||||
 | 
					        cy.createFeature_UI(featureToggleName, true, projectName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //when
 | 
				
			||||||
        cy.visit(
 | 
					        cy.visit(
 | 
				
			||||||
            `/projects/default/features/${featureToggleName}/strategies/create?environmentId=development&strategyName=flexibleRollout`
 | 
					            `/projects/${projectName}/features/${featureToggleName}/variants`
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cy.get("[data-testid='ADD_VARIANT_BUTTON']").first().click();
 | 
				
			||||||
        //then
 | 
					        //then
 | 
				
			||||||
        cy.get("[id='stickiness-select']")
 | 
					        cy.get("[id='stickiness-select']")
 | 
				
			||||||
            .first()
 | 
					            .first()
 | 
				
			||||||
            .should('have.text', 'userId');
 | 
					            .should('have.text', 'userId');
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('should respect the default project stickiness when creating a variant', () => {
 | 
					        //clean
 | 
				
			||||||
        createProject();
 | 
					        cy.deleteFeature_API(featureToggleName);
 | 
				
			||||||
        createFeature();
 | 
					        cy.deleteProject_API(projectName);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.visit(`/projects/default/features/${featureToggleName}/variants`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get("[data-testid='EDIT_VARIANTS_BUTTON']").click();
 | 
					 | 
				
			||||||
        //then
 | 
					 | 
				
			||||||
        cy.get('#menu-stickiness').first().should('have.text', 'userId');
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -1,43 +1,29 @@
 | 
				
			|||||||
/// <reference types="cypress" />
 | 
					///<reference path="../../global.d.ts" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const randomId = String(Math.random()).split('.')[1];
 | 
					const randomId = String(Math.random()).split('.')[1];
 | 
				
			||||||
const segmentName = `unleash-e2e-${randomId}`;
 | 
					const segmentName = `unleash-e2e-${randomId}`;
 | 
				
			||||||
 | 
					let segmentId: string;
 | 
				
			||||||
// Disable all active splash pages by visiting them.
 | 
					 | 
				
			||||||
const disableActiveSplashScreens = () => {
 | 
					 | 
				
			||||||
    cy.visit(`/splash/operators`);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('segments', () => {
 | 
					describe('segments', () => {
 | 
				
			||||||
    before(() => {
 | 
					    before(() => {
 | 
				
			||||||
        disableActiveSplashScreens();
 | 
					        cy.runBefore();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    beforeEach(() => {
 | 
					    beforeEach(() => {
 | 
				
			||||||
        cy.login();
 | 
					        cy.login_UI();
 | 
				
			||||||
        cy.visit('/segments');
 | 
					        cy.visit('/segments');
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it('can create a segment', () => {
 | 
					 | 
				
			||||||
        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
					        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
				
			||||||
            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
					            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cy.get("[data-testid='NAVIGATE_TO_CREATE_SEGMENT']").click();
 | 
					    it('can create a segment', () => {
 | 
				
			||||||
 | 
					        cy.createSegment_UI(segmentName);
 | 
				
			||||||
        cy.intercept('POST', '/api/admin/segments').as('createSegment');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get("[data-testid='SEGMENT_NAME_ID']").type(segmentName);
 | 
					 | 
				
			||||||
        cy.get("[data-testid='SEGMENT_DESC_ID']").type('hello-world');
 | 
					 | 
				
			||||||
        cy.get("[data-testid='SEGMENT_NEXT_BTN_ID']").click();
 | 
					 | 
				
			||||||
        cy.get("[data-testid='SEGMENT_CREATE_BTN_ID']").click();
 | 
					 | 
				
			||||||
        cy.wait('@createSegment');
 | 
					 | 
				
			||||||
        cy.contains(segmentName);
 | 
					        cy.contains(segmentName);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('gives an error if a segment exists with the same name', () => {
 | 
					    it('gives an error if a segment exists with the same name', () => {
 | 
				
			||||||
        cy.get("[data-testid='NAVIGATE_TO_CREATE_SEGMENT']").click();
 | 
					        cy.get("[data-testid='NAVIGATE_TO_CREATE_SEGMENT']").click();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get("[data-testid='SEGMENT_NAME_ID']").type(segmentName);
 | 
					        cy.get("[data-testid='SEGMENT_NAME_ID']").type(segmentName);
 | 
				
			||||||
        cy.get("[data-testid='SEGMENT_NEXT_BTN_ID']").should('be.disabled');
 | 
					        cy.get("[data-testid='SEGMENT_NEXT_BTN_ID']").should('be.disabled');
 | 
				
			||||||
        cy.get("[data-testid='INPUT_ERROR_TEXT']").contains(
 | 
					        cy.get("[data-testid='INPUT_ERROR_TEXT']").contains(
 | 
				
			||||||
@ -46,11 +32,7 @@ describe('segments', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('can delete a segment', () => {
 | 
					    it('can delete a segment', () => {
 | 
				
			||||||
        cy.get(`[data-testid='SEGMENT_DELETE_BTN_ID_${segmentName}']`).click();
 | 
					        cy.deleteSegment_UI(segmentName, segmentId);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.get("[data-testid='SEGMENT_DIALOG_NAME_ID']").type(segmentName);
 | 
					 | 
				
			||||||
        cy.get("[data-testid='DIALOGUE_CONFIRM_ID'").click();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cy.contains(segmentName).should('not.exist');
 | 
					        cy.contains(segmentName).should('not.exist');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										103
									
								
								frontend/cypress/support/API.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								frontend/cypress/support/API.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					///<reference path="../global.d.ts" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Chainable = Cypress.Chainable;
 | 
				
			||||||
 | 
					const baseUrl = Cypress.config().baseUrl;
 | 
				
			||||||
 | 
					const password = Cypress.env(`AUTH_PASSWORD`) + '_A';
 | 
				
			||||||
 | 
					const PROJECT_MEMBER = 5;
 | 
				
			||||||
 | 
					export const createFeature_API = (
 | 
				
			||||||
 | 
					    featureName: string,
 | 
				
			||||||
 | 
					    projectName?: string
 | 
				
			||||||
 | 
					): Chainable<any> => {
 | 
				
			||||||
 | 
					    const project = projectName || 'default';
 | 
				
			||||||
 | 
					    return cy.request({
 | 
				
			||||||
 | 
					        url: `/api/admin/projects/${project}/features`,
 | 
				
			||||||
 | 
					        method: 'POST',
 | 
				
			||||||
 | 
					        body: {
 | 
				
			||||||
 | 
					            name: `${featureName}`,
 | 
				
			||||||
 | 
					            description: 'hello-world',
 | 
				
			||||||
 | 
					            type: 'release',
 | 
				
			||||||
 | 
					            impressionData: false,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const deleteFeature_API = (name: string): Chainable<any> => {
 | 
				
			||||||
 | 
					    cy.request({
 | 
				
			||||||
 | 
					        method: 'DELETE',
 | 
				
			||||||
 | 
					        url: `${baseUrl}/api/admin/features/${name}`,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return cy.request({
 | 
				
			||||||
 | 
					        method: 'DELETE',
 | 
				
			||||||
 | 
					        url: `${baseUrl}/api/admin/archive/${name}`,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const createProject_API = (project: string): Chainable<any> => {
 | 
				
			||||||
 | 
					    return cy.request({
 | 
				
			||||||
 | 
					        url: `/api/admin/projects`,
 | 
				
			||||||
 | 
					        method: 'POST',
 | 
				
			||||||
 | 
					        body: {
 | 
				
			||||||
 | 
					            id: project,
 | 
				
			||||||
 | 
					            name: project,
 | 
				
			||||||
 | 
					            description: project,
 | 
				
			||||||
 | 
					            impressionData: false,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const deleteProject_API = (name: string): Chainable<any> => {
 | 
				
			||||||
 | 
					    return cy.request({
 | 
				
			||||||
 | 
					        method: 'DELETE',
 | 
				
			||||||
 | 
					        url: `${baseUrl}/api/admin/projects/${name}`,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const createUser_API = (userName: string, role: number) => {
 | 
				
			||||||
 | 
					    const name = `${userName}`;
 | 
				
			||||||
 | 
					    const email = `${name}@test.com`;
 | 
				
			||||||
 | 
					    const userIds: number[] = [];
 | 
				
			||||||
 | 
					    const userCredentials: Cypress.UserCredentials[] = [];
 | 
				
			||||||
 | 
					    cy.request('POST', `${baseUrl}/api/admin/user-admin`, {
 | 
				
			||||||
 | 
					        name: name,
 | 
				
			||||||
 | 
					        email: `${name}@test.com`,
 | 
				
			||||||
 | 
					        username: `${name}@test.com`,
 | 
				
			||||||
 | 
					        sendEmail: false,
 | 
				
			||||||
 | 
					        rootRole: role,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					        .as(name)
 | 
				
			||||||
 | 
					        .then(response => {
 | 
				
			||||||
 | 
					            const id = response.body.id;
 | 
				
			||||||
 | 
					            updateUserPassword_API(id).then(() => {
 | 
				
			||||||
 | 
					                addUserToProject_API(id, PROJECT_MEMBER).then(value => {
 | 
				
			||||||
 | 
					                    userIds.push(id);
 | 
				
			||||||
 | 
					                    userCredentials.push({ email, password });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    return cy.wrap({ userIds, userCredentials });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const updateUserPassword_API = (id: number, pass?: string): Chainable =>
 | 
				
			||||||
 | 
					    cy.request(
 | 
				
			||||||
 | 
					        'POST',
 | 
				
			||||||
 | 
					        `${baseUrl}/api/admin/user-admin/${id}/change-password`,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            password: pass || password,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addUserToProject_API = (
 | 
				
			||||||
 | 
					    id: number,
 | 
				
			||||||
 | 
					    role: number,
 | 
				
			||||||
 | 
					    projectName?: string
 | 
				
			||||||
 | 
					): Chainable => {
 | 
				
			||||||
 | 
					    const project = projectName || 'default';
 | 
				
			||||||
 | 
					    return cy.request(
 | 
				
			||||||
 | 
					        'POST',
 | 
				
			||||||
 | 
					        `${baseUrl}/api/admin/projects/${project}/role/${role}/access`,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            groups: [],
 | 
				
			||||||
 | 
					            users: [{ id }],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										347
									
								
								frontend/cypress/support/UI.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										347
									
								
								frontend/cypress/support/UI.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,347 @@
 | 
				
			|||||||
 | 
					///<reference path="../global.d.ts" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Chainable = Cypress.Chainable;
 | 
				
			||||||
 | 
					import AddStrategyOptions = Cypress.AddFlexibleRolloutStrategyOptions;
 | 
				
			||||||
 | 
					const AUTH_USER = Cypress.env('AUTH_USER');
 | 
				
			||||||
 | 
					const AUTH_PASSWORD = Cypress.env('AUTH_PASSWORD');
 | 
				
			||||||
 | 
					const ENTERPRISE = Boolean(Cypress.env('ENTERPRISE'));
 | 
				
			||||||
 | 
					const disableActiveSplashScreens = () => {
 | 
				
			||||||
 | 
					    return cy.visit(`/splash/operators`);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const disableFeatureStrategiesProdGuard = () => {
 | 
				
			||||||
 | 
					    localStorage.setItem(
 | 
				
			||||||
 | 
					        'useFeatureStrategyProdGuardSettings:v2',
 | 
				
			||||||
 | 
					        JSON.stringify({ hide: true })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const runBefore = () => {
 | 
				
			||||||
 | 
					    disableFeatureStrategiesProdGuard();
 | 
				
			||||||
 | 
					    disableActiveSplashScreens();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const login_UI = (
 | 
				
			||||||
 | 
					    user = AUTH_USER,
 | 
				
			||||||
 | 
					    password = AUTH_PASSWORD
 | 
				
			||||||
 | 
					): Chainable<any> => {
 | 
				
			||||||
 | 
					    return cy.session(user, () => {
 | 
				
			||||||
 | 
					        cy.visit('/');
 | 
				
			||||||
 | 
					        cy.wait(1500);
 | 
				
			||||||
 | 
					        cy.get("[data-testid='LOGIN_EMAIL_ID']").type(user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (AUTH_PASSWORD) {
 | 
				
			||||||
 | 
					            cy.get("[data-testid='LOGIN_PASSWORD_ID']").type(password);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cy.get("[data-testid='LOGIN_BUTTON']").click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Wait for the login redirect to complete.
 | 
				
			||||||
 | 
					        cy.get("[data-testid='HEADER_USER_AVATAR']");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
 | 
				
			||||||
 | 
					            cy.get("[data-testid='CLOSE_SPLASH']").click();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const createFeature_UI = (
 | 
				
			||||||
 | 
					    name: string,
 | 
				
			||||||
 | 
					    shouldWait?: boolean,
 | 
				
			||||||
 | 
					    project?: string
 | 
				
			||||||
 | 
					): Chainable<any> => {
 | 
				
			||||||
 | 
					    const projectName = project || 'default';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get('[data-testid=NAVIGATE_TO_CREATE_FEATURE').click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.intercept('POST', `/api/admin/projects/${projectName}/features`).as(
 | 
				
			||||||
 | 
					        'createFeature'
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.wait(300);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get("[data-testid='CF_NAME_ID'").type(name);
 | 
				
			||||||
 | 
					    cy.get("[data-testid='CF_DESC_ID'").type('hello-world');
 | 
				
			||||||
 | 
					    if (!shouldWait) return cy.get("[data-testid='CF_CREATE_BTN_ID']").click();
 | 
				
			||||||
 | 
					    else cy.get("[data-testid='CF_CREATE_BTN_ID']").click();
 | 
				
			||||||
 | 
					    return cy.wait('@createFeature');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const createProject_UI = (
 | 
				
			||||||
 | 
					    projectName: string,
 | 
				
			||||||
 | 
					    defaultStickiness: string
 | 
				
			||||||
 | 
					): Chainable<any> => {
 | 
				
			||||||
 | 
					    cy.get('[data-testid=NAVIGATE_TO_CREATE_PROJECT').click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.intercept('POST', `/api/admin/projects`).as('createProject');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get("[data-testid='PROJECT_ID_INPUT']").type(projectName);
 | 
				
			||||||
 | 
					    cy.get("[data-testid='PROJECT_NAME_INPUT']").type(projectName);
 | 
				
			||||||
 | 
					    cy.get("[id='stickiness-select']")
 | 
				
			||||||
 | 
					        .first()
 | 
				
			||||||
 | 
					        .click()
 | 
				
			||||||
 | 
					        .get(`[data-testid=SELECT_ITEM_ID-${defaultStickiness}`)
 | 
				
			||||||
 | 
					        .first()
 | 
				
			||||||
 | 
					        .click();
 | 
				
			||||||
 | 
					    cy.get("[data-testid='CREATE_PROJECT_BTN']").click();
 | 
				
			||||||
 | 
					    cy.wait('@createProject');
 | 
				
			||||||
 | 
					    return cy.visit(`/projects/${projectName}`);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const createSegment_UI = (segmentName: string): Chainable<any> => {
 | 
				
			||||||
 | 
					    cy.get("[data-testid='NAVIGATE_TO_CREATE_SEGMENT']").click();
 | 
				
			||||||
 | 
					    let segmentId;
 | 
				
			||||||
 | 
					    cy.intercept('POST', '/api/admin/segments', req => {
 | 
				
			||||||
 | 
					        req.continue(res => {
 | 
				
			||||||
 | 
					            segmentId = res.body.id;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }).as('createSegment');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get("[data-testid='SEGMENT_NAME_ID']").type(segmentName);
 | 
				
			||||||
 | 
					    cy.get("[data-testid='SEGMENT_DESC_ID']").type('hello-world');
 | 
				
			||||||
 | 
					    cy.get("[data-testid='SEGMENT_NEXT_BTN_ID']").click();
 | 
				
			||||||
 | 
					    cy.get("[data-testid='SEGMENT_CREATE_BTN_ID']").click();
 | 
				
			||||||
 | 
					    cy.wait('@createSegment');
 | 
				
			||||||
 | 
					    return cy.wrap(segmentId);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const deleteSegment_UI = (segmentName: string): Chainable<any> => {
 | 
				
			||||||
 | 
					    cy.get(`[data-testid='SEGMENT_DELETE_BTN_ID_${segmentName}']`).click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get("[data-testid='SEGMENT_DIALOG_NAME_ID']").type(segmentName);
 | 
				
			||||||
 | 
					    return cy.get("[data-testid='DIALOGUE_CONFIRM_ID'").click();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addFlexibleRolloutStrategyToFeature_UI = (
 | 
				
			||||||
 | 
					    options: AddStrategyOptions
 | 
				
			||||||
 | 
					): Chainable<any> => {
 | 
				
			||||||
 | 
					    const { featureToggleName, project, environment, stickiness } = options;
 | 
				
			||||||
 | 
					    const projectName = project || 'default';
 | 
				
			||||||
 | 
					    const env = environment || 'development';
 | 
				
			||||||
 | 
					    const defaultStickiness = stickiness || 'default';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.visit(`/projects/default/features/${featureToggleName}`);
 | 
				
			||||||
 | 
					    let strategyId;
 | 
				
			||||||
 | 
					    cy.intercept(
 | 
				
			||||||
 | 
					        'POST',
 | 
				
			||||||
 | 
					        `/api/admin/projects/${projectName}/features/${featureToggleName}/environments/development/strategies`,
 | 
				
			||||||
 | 
					        req => {
 | 
				
			||||||
 | 
					            expect(req.body.name).to.equal('flexibleRollout');
 | 
				
			||||||
 | 
					            expect(req.body.parameters.groupId).to.equal(featureToggleName);
 | 
				
			||||||
 | 
					            expect(req.body.parameters.stickiness).to.equal(defaultStickiness);
 | 
				
			||||||
 | 
					            expect(req.body.parameters.rollout).to.equal('50');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (ENTERPRISE) {
 | 
				
			||||||
 | 
					                expect(req.body.constraints.length).to.equal(1);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                expect(req.body.constraints.length).to.equal(0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            req.continue(res => {
 | 
				
			||||||
 | 
					                strategyId = res.body.id;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ).as('addStrategyToFeature');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.visit(
 | 
				
			||||||
 | 
					        `/projects/${projectName}/features/${featureToggleName}/strategies/create?environmentId=${env}&strategyName=flexibleRollout`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    cy.wait(500);
 | 
				
			||||||
 | 
					    //  Takes a bit to load the screen - this will wait until it finds it or fail
 | 
				
			||||||
 | 
					    cy.get('[data-testid=FLEXIBLE_STRATEGY_STICKINESS_ID]');
 | 
				
			||||||
 | 
					    if (ENTERPRISE) {
 | 
				
			||||||
 | 
					        cy.get('[data-testid=ADD_CONSTRAINT_ID]').click();
 | 
				
			||||||
 | 
					        cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    cy.get(`[data-testid=STRATEGY_FORM_SUBMIT_ID]`).first().click();
 | 
				
			||||||
 | 
					    cy.wait('@addStrategyToFeature');
 | 
				
			||||||
 | 
					    return cy.wrap(strategyId);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const updateFlexibleRolloutStrategy_UI = (
 | 
				
			||||||
 | 
					    featureToggleName: string,
 | 
				
			||||||
 | 
					    strategyId: string,
 | 
				
			||||||
 | 
					    projectName?: string
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					    const project = projectName || 'default';
 | 
				
			||||||
 | 
					    cy.visit(
 | 
				
			||||||
 | 
					        `/projects/${project}/features/${featureToggleName}/strategies/edit?environmentId=development&strategyId=${strategyId}`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.wait(500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get('[data-testid=FLEXIBLE_STRATEGY_STICKINESS_ID]')
 | 
				
			||||||
 | 
					        .first()
 | 
				
			||||||
 | 
					        .click()
 | 
				
			||||||
 | 
					        .get('[data-testid=SELECT_ITEM_ID-sessionId')
 | 
				
			||||||
 | 
					        .first()
 | 
				
			||||||
 | 
					        .click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.wait(500);
 | 
				
			||||||
 | 
					    cy.get('[data-testid=FLEXIBLE_STRATEGY_GROUP_ID]')
 | 
				
			||||||
 | 
					        .first()
 | 
				
			||||||
 | 
					        .clear()
 | 
				
			||||||
 | 
					        .type('new-group-id');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get(`[data-testid=STRATEGY_FORM_SUBMIT_ID]`).first().click();
 | 
				
			||||||
 | 
					    cy.intercept(
 | 
				
			||||||
 | 
					        'PUT',
 | 
				
			||||||
 | 
					        `/api/admin/projects/${project}/features/${featureToggleName}/environments/*/strategies/${strategyId}`,
 | 
				
			||||||
 | 
					        req => {
 | 
				
			||||||
 | 
					            expect(req.body.parameters.groupId).to.equal('new-group-id');
 | 
				
			||||||
 | 
					            expect(req.body.parameters.stickiness).to.equal('sessionId');
 | 
				
			||||||
 | 
					            expect(req.body.parameters.rollout).to.equal('50');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (ENTERPRISE) {
 | 
				
			||||||
 | 
					                expect(req.body.constraints.length).to.equal(1);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                expect(req.body.constraints.length).to.equal(0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            req.continue(res => {
 | 
				
			||||||
 | 
					                expect(res.statusCode).to.equal(200);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ).as('updateStrategy');
 | 
				
			||||||
 | 
					    return cy.wait('@updateStrategy');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const deleteFeatureStrategy_UI = (
 | 
				
			||||||
 | 
					    featureToggleName: string,
 | 
				
			||||||
 | 
					    strategyId: string,
 | 
				
			||||||
 | 
					    shouldWait?: boolean,
 | 
				
			||||||
 | 
					    projectName?: string
 | 
				
			||||||
 | 
					): Chainable<any> => {
 | 
				
			||||||
 | 
					    const project = projectName || 'default';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.intercept(
 | 
				
			||||||
 | 
					        'DELETE',
 | 
				
			||||||
 | 
					        `/api/admin/projects/${project}/features/${featureToggleName}/environments/*/strategies/${strategyId}`,
 | 
				
			||||||
 | 
					        req => {
 | 
				
			||||||
 | 
					            req.continue(res => {
 | 
				
			||||||
 | 
					                expect(res.statusCode).to.equal(200);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ).as('deleteUserStrategy');
 | 
				
			||||||
 | 
					    cy.visit(`/projects/${project}/features/${featureToggleName}`);
 | 
				
			||||||
 | 
					    cy.get('[data-testid=FEATURE_ENVIRONMENT_ACCORDION_development]').click();
 | 
				
			||||||
 | 
					    cy.get('[data-testid=STRATEGY_FORM_REMOVE_ID]').click();
 | 
				
			||||||
 | 
					    if (!shouldWait) return cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
				
			||||||
 | 
					    else cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
				
			||||||
 | 
					    return cy.wait('@deleteUserStrategy');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addUserIdStrategyToFeature_UI = (
 | 
				
			||||||
 | 
					    featureToggleName: string,
 | 
				
			||||||
 | 
					    projectName: string
 | 
				
			||||||
 | 
					): Chainable<any> => {
 | 
				
			||||||
 | 
					    const project = projectName || 'default';
 | 
				
			||||||
 | 
					    cy.visit(
 | 
				
			||||||
 | 
					        `/projects/${project}/features/${featureToggleName}/strategies/create?environmentId=development&strategyName=userWithId`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ENTERPRISE) {
 | 
				
			||||||
 | 
					        cy.get('[data-testid=ADD_CONSTRAINT_ID]').click();
 | 
				
			||||||
 | 
					        cy.get('[data-testid=CONSTRAINT_AUTOCOMPLETE_ID]')
 | 
				
			||||||
 | 
					            .type('{downArrow}'.repeat(1))
 | 
				
			||||||
 | 
					            .type('{enter}');
 | 
				
			||||||
 | 
					        cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get('[data-testid=STRATEGY_INPUT_LIST]')
 | 
				
			||||||
 | 
					        .type('user1')
 | 
				
			||||||
 | 
					        .type('{enter}')
 | 
				
			||||||
 | 
					        .type('user2')
 | 
				
			||||||
 | 
					        .type('{enter}');
 | 
				
			||||||
 | 
					    cy.get('[data-testid=ADD_TO_STRATEGY_INPUT_LIST]').click();
 | 
				
			||||||
 | 
					    let strategyId;
 | 
				
			||||||
 | 
					    cy.intercept(
 | 
				
			||||||
 | 
					        'POST',
 | 
				
			||||||
 | 
					        `/api/admin/projects/default/features/${featureToggleName}/environments/*/strategies`,
 | 
				
			||||||
 | 
					        req => {
 | 
				
			||||||
 | 
					            expect(req.body.name).to.equal('userWithId');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            expect(req.body.parameters.userIds.length).to.equal(11);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (ENTERPRISE) {
 | 
				
			||||||
 | 
					                expect(req.body.constraints.length).to.equal(1);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                expect(req.body.constraints.length).to.equal(0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            req.continue(res => {
 | 
				
			||||||
 | 
					                strategyId = res.body.id;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ).as('addStrategyToFeature');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get(`[data-testid=STRATEGY_FORM_SUBMIT_ID]`).first().click();
 | 
				
			||||||
 | 
					    cy.wait('@addStrategyToFeature');
 | 
				
			||||||
 | 
					    return cy.wrap(strategyId);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addVariantsToFeature_UI = (
 | 
				
			||||||
 | 
					    featureToggleName: string,
 | 
				
			||||||
 | 
					    variants: Array<string>,
 | 
				
			||||||
 | 
					    projectName: string
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					    const project = projectName || 'default';
 | 
				
			||||||
 | 
					    cy.visit(`/projects/${project}/features/${featureToggleName}/variants`);
 | 
				
			||||||
 | 
					    cy.wait(1000);
 | 
				
			||||||
 | 
					    cy.intercept(
 | 
				
			||||||
 | 
					        'PATCH',
 | 
				
			||||||
 | 
					        `/api/admin/projects/${project}/features/${featureToggleName}/environments/development/variants`,
 | 
				
			||||||
 | 
					        req => {
 | 
				
			||||||
 | 
					            variants.forEach((variant, index) => {
 | 
				
			||||||
 | 
					                expect(req.body[index].op).to.equal('add');
 | 
				
			||||||
 | 
					                expect(req.body[index].path).to.equal(`/${index}`);
 | 
				
			||||||
 | 
					                expect(req.body[index].value.name).to.equal(variant);
 | 
				
			||||||
 | 
					                expect(req.body[index].value.weight).to.equal(
 | 
				
			||||||
 | 
					                    1000 / variants.length
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ).as('variantCreation');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get('[data-testid=ADD_VARIANT_BUTTON]').first().click();
 | 
				
			||||||
 | 
					    cy.wait(500);
 | 
				
			||||||
 | 
					    variants.forEach((variant, index) => {
 | 
				
			||||||
 | 
					        cy.get('[data-testid=VARIANT_NAME_INPUT]').eq(index).type(variant);
 | 
				
			||||||
 | 
					        index + 1 < variants.length &&
 | 
				
			||||||
 | 
					            cy.get('[data-testid=MODAL_ADD_VARIANT_BUTTON]').first().click();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').first().click();
 | 
				
			||||||
 | 
					    return cy.wait('@variantCreation');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const deleteVariant_UI = (
 | 
				
			||||||
 | 
					    featureToggleName: string,
 | 
				
			||||||
 | 
					    variant: string,
 | 
				
			||||||
 | 
					    projectName?: string
 | 
				
			||||||
 | 
					): Chainable<any> => {
 | 
				
			||||||
 | 
					    const project = projectName || 'default';
 | 
				
			||||||
 | 
					    cy.visit(`/projects/${project}/features/${featureToggleName}/variants`);
 | 
				
			||||||
 | 
					    cy.get('[data-testid=EDIT_VARIANTS_BUTTON]').click();
 | 
				
			||||||
 | 
					    cy.wait(300);
 | 
				
			||||||
 | 
					    cy.get(`[data-testid=VARIANT_DELETE_BUTTON_${variant}]`).first().click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.intercept(
 | 
				
			||||||
 | 
					        'PATCH',
 | 
				
			||||||
 | 
					        `/api/admin/projects/${project}/features/${featureToggleName}/environments/development/variants`,
 | 
				
			||||||
 | 
					        req => {
 | 
				
			||||||
 | 
					            expect(req.body[0].op).to.equal('remove');
 | 
				
			||||||
 | 
					            expect(req.body[0].path).to.equal('/1');
 | 
				
			||||||
 | 
					            expect(req.body[1].op).to.equal('replace');
 | 
				
			||||||
 | 
					            expect(req.body[1].path).to.equal('/0/weight');
 | 
				
			||||||
 | 
					            expect(req.body[1].value).to.equal(1000);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ).as('delete');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click();
 | 
				
			||||||
 | 
					    return cy.wait('@delete');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const logout_UI = (): Chainable<any> => {
 | 
				
			||||||
 | 
					    return cy.visit('/logout');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,49 +1,57 @@
 | 
				
			|||||||
// ***********************************************
 | 
					///<reference path="../global.d.ts" />
 | 
				
			||||||
// This example commands.js shows you how to
 | 
					 | 
				
			||||||
// create various custom commands and overwrite
 | 
					 | 
				
			||||||
// existing commands.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// For more comprehensive examples of custom
 | 
					 | 
				
			||||||
// commands please read more here:
 | 
					 | 
				
			||||||
// https://on.cypress.io/custom-commands
 | 
					 | 
				
			||||||
// ***********************************************
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// -- This is a parent command --
 | 
					 | 
				
			||||||
// Cypress.Commands.add('login', (email, password) => { ... })
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// -- This is a child command --
 | 
					 | 
				
			||||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// -- This is a dual command --
 | 
					 | 
				
			||||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// -- This will overwrite an existing command --
 | 
					 | 
				
			||||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const AUTH_USER = Cypress.env('AUTH_USER');
 | 
					import {
 | 
				
			||||||
const AUTH_PASSWORD = Cypress.env('AUTH_PASSWORD');
 | 
					    runBefore,
 | 
				
			||||||
 | 
					    login_UI,
 | 
				
			||||||
 | 
					    logout_UI,
 | 
				
			||||||
 | 
					    createProject_UI,
 | 
				
			||||||
 | 
					    createFeature_UI,
 | 
				
			||||||
 | 
					    createSegment_UI,
 | 
				
			||||||
 | 
					    deleteSegment_UI,
 | 
				
			||||||
 | 
					    deleteVariant_UI,
 | 
				
			||||||
 | 
					    deleteFeatureStrategy_UI,
 | 
				
			||||||
 | 
					    addFlexibleRolloutStrategyToFeature_UI,
 | 
				
			||||||
 | 
					    addUserIdStrategyToFeature_UI,
 | 
				
			||||||
 | 
					    updateFlexibleRolloutStrategy_UI,
 | 
				
			||||||
 | 
					    addVariantsToFeature_UI,
 | 
				
			||||||
 | 
					    //@ts-ignore
 | 
				
			||||||
 | 
					} from './UI';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    addUserToProject_API,
 | 
				
			||||||
 | 
					    createFeature_API,
 | 
				
			||||||
 | 
					    createProject_API,
 | 
				
			||||||
 | 
					    createUser_API,
 | 
				
			||||||
 | 
					    deleteFeature_API,
 | 
				
			||||||
 | 
					    deleteProject_API,
 | 
				
			||||||
 | 
					    updateUserPassword_API,
 | 
				
			||||||
 | 
					    //@ts-ignore
 | 
				
			||||||
 | 
					} from './API';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Cypress.Commands.add('login', (user = AUTH_USER, password = AUTH_PASSWORD) =>
 | 
					Cypress.Commands.add('runBefore', runBefore);
 | 
				
			||||||
    cy.session(user, () => {
 | 
					Cypress.Commands.add('login_UI', login_UI);
 | 
				
			||||||
        cy.visit('/');
 | 
					Cypress.Commands.add('createSegment_UI', createSegment_UI);
 | 
				
			||||||
        cy.wait(1500);
 | 
					Cypress.Commands.add('deleteSegment_UI', deleteSegment_UI);
 | 
				
			||||||
        cy.get("[data-testid='LOGIN_EMAIL_ID']").type(user);
 | 
					Cypress.Commands.add('deleteFeature_API', deleteFeature_API);
 | 
				
			||||||
 | 
					Cypress.Commands.add('deleteProject_API', deleteProject_API);
 | 
				
			||||||
        if (AUTH_PASSWORD) {
 | 
					Cypress.Commands.add('logout_UI', logout_UI);
 | 
				
			||||||
            cy.get("[data-testid='LOGIN_PASSWORD_ID']").type(password);
 | 
					Cypress.Commands.add('createProject_UI', createProject_UI);
 | 
				
			||||||
        }
 | 
					Cypress.Commands.add('createUser_API', createUser_API);
 | 
				
			||||||
 | 
					Cypress.Commands.add('addUserToProject_API', addUserToProject_API);
 | 
				
			||||||
        cy.get("[data-testid='LOGIN_BUTTON']").click();
 | 
					Cypress.Commands.add('updateUserPassword_API', updateUserPassword_API);
 | 
				
			||||||
 | 
					Cypress.Commands.add('createFeature_UI', createFeature_UI);
 | 
				
			||||||
        // Wait for the login redirect to complete.
 | 
					Cypress.Commands.add('deleteFeatureStrategy_UI', deleteFeatureStrategy_UI);
 | 
				
			||||||
        cy.get("[data-testid='HEADER_USER_AVATAR']");
 | 
					Cypress.Commands.add('createFeature_API', createFeature_API);
 | 
				
			||||||
    })
 | 
					Cypress.Commands.add('deleteVariant_UI', deleteVariant_UI);
 | 
				
			||||||
 | 
					Cypress.Commands.add('addVariantsToFeature_UI', addVariantsToFeature_UI);
 | 
				
			||||||
 | 
					Cypress.Commands.add(
 | 
				
			||||||
 | 
					    'addUserIdStrategyToFeature_UI',
 | 
				
			||||||
 | 
					    addUserIdStrategyToFeature_UI
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					Cypress.Commands.add(
 | 
				
			||||||
 | 
					    'addFlexibleRolloutStrategyToFeature_UI',
 | 
				
			||||||
 | 
					    addFlexibleRolloutStrategyToFeature_UI
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					Cypress.Commands.add(
 | 
				
			||||||
 | 
					    'updateFlexibleRolloutStrategy_UI',
 | 
				
			||||||
 | 
					    updateFlexibleRolloutStrategy_UI
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					 | 
				
			||||||
Cypress.Commands.add('logout', () => {
 | 
					 | 
				
			||||||
    cy.visit('/logout');
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -14,16 +14,8 @@
 | 
				
			|||||||
// ***********************************************************
 | 
					// ***********************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Import commands.js using ES2015 syntax:
 | 
					// Import commands.js using ES2015 syntax:
 | 
				
			||||||
 | 
					// @ts-ignore
 | 
				
			||||||
import './commands';
 | 
					import './commands';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Alternatively you can use CommonJS syntax:
 | 
					// Alternatively you can use CommonJS syntax:
 | 
				
			||||||
// require('./commands')
 | 
					// require('./commands')
 | 
				
			||||||
 | 
					 | 
				
			||||||
declare global {
 | 
					 | 
				
			||||||
    namespace Cypress {
 | 
					 | 
				
			||||||
        interface Chainable {
 | 
					 | 
				
			||||||
            login(user?: string, password?: string): Chainable<null>;
 | 
					 | 
				
			||||||
            logout(user?: string): Chainable<null>;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								frontend/cypress/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								frontend/cypress/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "extends": "../tsconfig.json",
 | 
				
			||||||
 | 
					  "include": ["./**/*.ts", "../cypress.d.ts"],
 | 
				
			||||||
 | 
					  "exclude": [],
 | 
				
			||||||
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "types": ["cypress"],
 | 
				
			||||||
 | 
					    "lib": ["es2015", "dom"],
 | 
				
			||||||
 | 
					    "isolatedModules": false,
 | 
				
			||||||
 | 
					    "allowJs": true,
 | 
				
			||||||
 | 
					    "noEmit": true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -20,6 +20,7 @@ import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashCon
 | 
				
			|||||||
import { updateWeightEdit } from 'component/common/util';
 | 
					import { updateWeightEdit } from 'component/common/util';
 | 
				
			||||||
import { StickinessSelect } from 'component/feature/StrategyTypes/FlexibleStrategy/StickinessSelect/StickinessSelect';
 | 
					import { StickinessSelect } from 'component/feature/StrategyTypes/FlexibleStrategy/StickinessSelect/StickinessSelect';
 | 
				
			||||||
import { useDefaultProjectSettings } from 'hooks/useDefaultProjectSettings';
 | 
					import { useDefaultProjectSettings } from 'hooks/useDefaultProjectSettings';
 | 
				
			||||||
 | 
					import Loader from 'component/common/Loader/Loader';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const StyledFormSubtitle = styled('div')(({ theme }) => ({
 | 
					const StyledFormSubtitle = styled('div')(({ theme }) => ({
 | 
				
			||||||
    display: 'flex',
 | 
					    display: 'flex',
 | 
				
			||||||
@ -145,7 +146,7 @@ export const EnvironmentVariantsModal = ({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const { uiConfig } = useUiConfig();
 | 
					    const { uiConfig } = useUiConfig();
 | 
				
			||||||
    const { context } = useUnleashContext();
 | 
					    const { context } = useUnleashContext();
 | 
				
			||||||
    const { defaultStickiness } = useDefaultProjectSettings(projectId);
 | 
					    const { defaultStickiness, loading } = useDefaultProjectSettings(projectId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
 | 
					    const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
 | 
				
			||||||
    const { data } = usePendingChangeRequests(projectId);
 | 
					    const { data } = usePendingChangeRequests(projectId);
 | 
				
			||||||
@ -157,6 +158,7 @@ export const EnvironmentVariantsModal = ({
 | 
				
			|||||||
    const [newVariant, setNewVariant] = useState<string>();
 | 
					    const [newVariant, setNewVariant] = useState<string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (!loading) {
 | 
				
			||||||
            setVariantsEdit(
 | 
					            setVariantsEdit(
 | 
				
			||||||
                oldVariants.length
 | 
					                oldVariants.length
 | 
				
			||||||
                    ? oldVariants.map(oldVariant => ({
 | 
					                    ? oldVariants.map(oldVariant => ({
 | 
				
			||||||
@ -181,7 +183,8 @@ export const EnvironmentVariantsModal = ({
 | 
				
			|||||||
                          },
 | 
					                          },
 | 
				
			||||||
                      ]
 | 
					                      ]
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
    }, [open]);
 | 
					        }
 | 
				
			||||||
 | 
					    }, [open, loading]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const updateVariant = (updatedVariant: IFeatureVariantEdit, id: string) => {
 | 
					    const updateVariant = (updatedVariant: IFeatureVariantEdit, id: string) => {
 | 
				
			||||||
        setVariantsEdit(prevVariants =>
 | 
					        setVariantsEdit(prevVariants =>
 | 
				
			||||||
@ -303,6 +306,11 @@ export const EnvironmentVariantsModal = ({
 | 
				
			|||||||
            setError(apiPayload.error);
 | 
					            setError(apiPayload.error);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }, [apiPayload.error]);
 | 
					    }, [apiPayload.error]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (loading || stickiness === '') {
 | 
				
			||||||
 | 
					        return <Loader />;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <SidebarModal
 | 
					        <SidebarModal
 | 
				
			||||||
            open={open}
 | 
					            open={open}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ import { StickinessSelect } from './StickinessSelect/StickinessSelect';
 | 
				
			|||||||
import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
 | 
					import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
 | 
				
			||||||
import { useDefaultProjectSettings } from 'hooks/useDefaultProjectSettings';
 | 
					import { useDefaultProjectSettings } from 'hooks/useDefaultProjectSettings';
 | 
				
			||||||
import Loader from '../../../common/Loader/Loader';
 | 
					import Loader from '../../../common/Loader/Loader';
 | 
				
			||||||
 | 
					import { useMemo } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IFlexibleStrategyProps {
 | 
					interface IFlexibleStrategyProps {
 | 
				
			||||||
    parameters: IFeatureStrategyParameters;
 | 
					    parameters: IFeatureStrategyParameters;
 | 
				
			||||||
@ -30,6 +31,7 @@ const FlexibleStrategy = ({
 | 
				
			|||||||
}: IFlexibleStrategyProps) => {
 | 
					}: IFlexibleStrategyProps) => {
 | 
				
			||||||
    const projectId = useOptionalPathParam('projectId');
 | 
					    const projectId = useOptionalPathParam('projectId');
 | 
				
			||||||
    const { defaultStickiness, loading } = useDefaultProjectSettings(projectId);
 | 
					    const { defaultStickiness, loading } = useDefaultProjectSettings(projectId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onUpdate = (field: string) => (newValue: string) => {
 | 
					    const onUpdate = (field: string) => (newValue: string) => {
 | 
				
			||||||
        updateParameter(field, newValue);
 | 
					        updateParameter(field, newValue);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@ -43,15 +45,13 @@ const FlexibleStrategy = ({
 | 
				
			|||||||
            ? parseParameterNumber(parameters.rollout)
 | 
					            ? parseParameterNumber(parameters.rollout)
 | 
				
			||||||
            : 100;
 | 
					            : 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const resolveStickiness = () => {
 | 
					    const stickiness = useMemo(() => {
 | 
				
			||||||
        if (parameters.stickiness === '') {
 | 
					        if (parameters.stickiness === '' && !loading) {
 | 
				
			||||||
            return defaultStickiness;
 | 
					            return defaultStickiness;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return parseParameterString(parameters.stickiness);
 | 
					        return parseParameterString(parameters.stickiness);
 | 
				
			||||||
    };
 | 
					    }, [loading, parameters.stickiness]);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const stickiness = resolveStickiness();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (parameters.stickiness === '') {
 | 
					    if (parameters.stickiness === '') {
 | 
				
			||||||
        onUpdate('stickiness')(stickiness);
 | 
					        onUpdate('stickiness')(stickiness);
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
 | 
				
			|||||||
import { StickinessSelect } from 'component/feature/StrategyTypes/FlexibleStrategy/StickinessSelect/StickinessSelect';
 | 
					import { StickinessSelect } from 'component/feature/StrategyTypes/FlexibleStrategy/StickinessSelect/StickinessSelect';
 | 
				
			||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
					import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
				
			||||||
import Select from 'component/common/select';
 | 
					import Select from 'component/common/select';
 | 
				
			||||||
import { DefaultStickiness, ProjectMode } from '../hooks/useProjectForm';
 | 
					import { ProjectMode } from '../hooks/useProjectForm';
 | 
				
			||||||
import { Box } from '@mui/material';
 | 
					import { Box } from '@mui/material';
 | 
				
			||||||
import { CollaborationModeTooltip } from './CollaborationModeTooltip';
 | 
					import { CollaborationModeTooltip } from './CollaborationModeTooltip';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -23,9 +23,7 @@ interface IProjectForm {
 | 
				
			|||||||
    projectDesc: string;
 | 
					    projectDesc: string;
 | 
				
			||||||
    projectStickiness?: string;
 | 
					    projectStickiness?: string;
 | 
				
			||||||
    projectMode?: string;
 | 
					    projectMode?: string;
 | 
				
			||||||
    setProjectStickiness?: React.Dispatch<
 | 
					    setProjectStickiness?: React.Dispatch<React.SetStateAction<string>>;
 | 
				
			||||||
        React.SetStateAction<DefaultStickiness>
 | 
					 | 
				
			||||||
    >;
 | 
					 | 
				
			||||||
    setProjectMode?: React.Dispatch<React.SetStateAction<ProjectMode>>;
 | 
					    setProjectMode?: React.Dispatch<React.SetStateAction<ProjectMode>>;
 | 
				
			||||||
    setProjectId: React.Dispatch<React.SetStateAction<string>>;
 | 
					    setProjectId: React.Dispatch<React.SetStateAction<string>>;
 | 
				
			||||||
    setProjectName: React.Dispatch<React.SetStateAction<string>>;
 | 
					    setProjectName: React.Dispatch<React.SetStateAction<string>>;
 | 
				
			||||||
@ -127,9 +125,7 @@ const ProjectForm: React.FC<IProjectForm> = ({
 | 
				
			|||||||
                                data-testid={PROJECT_STICKINESS_SELECT}
 | 
					                                data-testid={PROJECT_STICKINESS_SELECT}
 | 
				
			||||||
                                onChange={e =>
 | 
					                                onChange={e =>
 | 
				
			||||||
                                    setProjectStickiness &&
 | 
					                                    setProjectStickiness &&
 | 
				
			||||||
                                    setProjectStickiness(
 | 
					                                    setProjectStickiness(e.target.value)
 | 
				
			||||||
                                        e.target.value as DefaultStickiness
 | 
					 | 
				
			||||||
                                    )
 | 
					 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                                editable
 | 
					                                editable
 | 
				
			||||||
                            />
 | 
					                            />
 | 
				
			||||||
 | 
				
			|||||||
@ -1,26 +1,22 @@
 | 
				
			|||||||
import { useEffect, useState } from 'react';
 | 
					import { useEffect, useState } from 'react';
 | 
				
			||||||
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
 | 
					import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
 | 
				
			||||||
import { formatUnknownError } from 'utils/formatUnknownError';
 | 
					import { formatUnknownError } from 'utils/formatUnknownError';
 | 
				
			||||||
import { useDefaultProjectSettings } from 'hooks/useDefaultProjectSettings';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ProjectMode = 'open' | 'protected';
 | 
					export type ProjectMode = 'open' | 'protected';
 | 
				
			||||||
export type DefaultStickiness = 'default' | 'userId' | 'sessionId' | 'random';
 | 
					 | 
				
			||||||
export const DEFAULT_PROJECT_STICKINESS = 'default';
 | 
					export const DEFAULT_PROJECT_STICKINESS = 'default';
 | 
				
			||||||
const useProjectForm = (
 | 
					const useProjectForm = (
 | 
				
			||||||
    initialProjectId = '',
 | 
					    initialProjectId = '',
 | 
				
			||||||
    initialProjectName = '',
 | 
					    initialProjectName = '',
 | 
				
			||||||
    initialProjectDesc = '',
 | 
					    initialProjectDesc = '',
 | 
				
			||||||
    initialProjectStickiness: DefaultStickiness = DEFAULT_PROJECT_STICKINESS,
 | 
					    initialProjectStickiness = DEFAULT_PROJECT_STICKINESS,
 | 
				
			||||||
    initialProjectMode: ProjectMode = 'open'
 | 
					    initialProjectMode: ProjectMode = 'open'
 | 
				
			||||||
) => {
 | 
					) => {
 | 
				
			||||||
    const [projectId, setProjectId] = useState(initialProjectId);
 | 
					    const [projectId, setProjectId] = useState(initialProjectId);
 | 
				
			||||||
    const { defaultStickiness } = useDefaultProjectSettings(projectId);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [projectName, setProjectName] = useState(initialProjectName);
 | 
					    const [projectName, setProjectName] = useState(initialProjectName);
 | 
				
			||||||
    const [projectDesc, setProjectDesc] = useState(initialProjectDesc);
 | 
					    const [projectDesc, setProjectDesc] = useState(initialProjectDesc);
 | 
				
			||||||
    const [projectStickiness, setProjectStickiness] =
 | 
					    const [projectStickiness, setProjectStickiness] = useState<string>(
 | 
				
			||||||
        useState<DefaultStickiness>(
 | 
					        initialProjectStickiness
 | 
				
			||||||
            defaultStickiness || initialProjectStickiness
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const [projectMode, setProjectMode] =
 | 
					    const [projectMode, setProjectMode] =
 | 
				
			||||||
        useState<ProjectMode>(initialProjectMode);
 | 
					        useState<ProjectMode>(initialProjectMode);
 | 
				
			||||||
 | 
				
			|||||||
@ -3,14 +3,11 @@ import { SWRConfiguration } from 'swr';
 | 
				
			|||||||
import { useCallback } from 'react';
 | 
					import { useCallback } from 'react';
 | 
				
			||||||
import handleErrorResponses from './api/getters/httpErrorResponseHandler';
 | 
					import handleErrorResponses from './api/getters/httpErrorResponseHandler';
 | 
				
			||||||
import { useConditionalSWR } from './api/getters/useConditionalSWR/useConditionalSWR';
 | 
					import { useConditionalSWR } from './api/getters/useConditionalSWR/useConditionalSWR';
 | 
				
			||||||
import {
 | 
					import { ProjectMode } from 'component/project/Project/hooks/useProjectForm';
 | 
				
			||||||
    DefaultStickiness,
 | 
					 | 
				
			||||||
    ProjectMode,
 | 
					 | 
				
			||||||
} from 'component/project/Project/hooks/useProjectForm';
 | 
					 | 
				
			||||||
import { formatApiPath } from 'utils/formatPath';
 | 
					import { formatApiPath } from 'utils/formatPath';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ISettingsResponse {
 | 
					export interface ISettingsResponse {
 | 
				
			||||||
    defaultStickiness?: DefaultStickiness;
 | 
					    defaultStickiness?: string;
 | 
				
			||||||
    mode?: ProjectMode;
 | 
					    mode?: ProjectMode;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
const DEFAULT_STICKINESS = 'default';
 | 
					const DEFAULT_STICKINESS = 'default';
 | 
				
			||||||
@ -23,7 +20,8 @@ export const useDefaultProjectSettings = (
 | 
				
			|||||||
    const PATH = `api/admin/projects/${projectId}/settings`;
 | 
					    const PATH = `api/admin/projects/${projectId}/settings`;
 | 
				
			||||||
    const { projectScopedStickiness } = uiConfig.flags;
 | 
					    const { projectScopedStickiness } = uiConfig.flags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { data, error, mutate } = useConditionalSWR<ISettingsResponse>(
 | 
					    const { data, isLoading, error, mutate } =
 | 
				
			||||||
 | 
					        useConditionalSWR<ISettingsResponse>(
 | 
				
			||||||
            Boolean(projectId) && Boolean(projectScopedStickiness),
 | 
					            Boolean(projectId) && Boolean(projectScopedStickiness),
 | 
				
			||||||
            {},
 | 
					            {},
 | 
				
			||||||
            ['useDefaultProjectSettings', PATH],
 | 
					            ['useDefaultProjectSettings', PATH],
 | 
				
			||||||
@ -31,16 +29,23 @@ export const useDefaultProjectSettings = (
 | 
				
			|||||||
            options
 | 
					            options
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const defaultStickiness: DefaultStickiness =
 | 
					    const defaultStickiness = (): string => {
 | 
				
			||||||
        data?.defaultStickiness ?? DEFAULT_STICKINESS;
 | 
					        if (!isLoading) {
 | 
				
			||||||
 | 
					            if (data?.defaultStickiness) {
 | 
				
			||||||
 | 
					                return data?.defaultStickiness;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return DEFAULT_STICKINESS;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return '';
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const refetch = useCallback(() => {
 | 
					    const refetch = useCallback(() => {
 | 
				
			||||||
        mutate().catch(console.warn);
 | 
					        mutate().catch(console.warn);
 | 
				
			||||||
    }, [mutate]);
 | 
					    }, [mutate]);
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        defaultStickiness,
 | 
					        defaultStickiness: defaultStickiness(),
 | 
				
			||||||
        refetch,
 | 
					        refetch,
 | 
				
			||||||
        loading: !error && !data,
 | 
					        loading: isLoading,
 | 
				
			||||||
        error,
 | 
					        error,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -18,8 +18,9 @@
 | 
				
			|||||||
    "strict": true,
 | 
					    "strict": true,
 | 
				
			||||||
    "paths": {
 | 
					    "paths": {
 | 
				
			||||||
      "@server/*": ["./../../src/lib/*"]
 | 
					      "@server/*": ["./../../src/lib/*"]
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  "include": ["./src"],
 | 
					    "types": ["cypress"]
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "include": ["./src", "cypress.d.ts"],
 | 
				
			||||||
  "references": [{ "path": "./tsconfig.node.json" }]
 | 
					  "references": [{ "path": "./tsconfig.node.json" }]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user