Merge pull request #1968 from Unleash/merge-frontend-with-backend-2
Merge frontend with backend
| @ -9,3 +9,5 @@ | ||||
| !CHANGELOG.md | ||||
| !LICENSE | ||||
| !README.md | ||||
| !frontend | ||||
| frontend/node_modules | ||||
|  | ||||
| @ -10,3 +10,4 @@ website/core | ||||
| website/pages | ||||
| website | ||||
| setupJest.js | ||||
| frontend | ||||
|  | ||||
							
								
								
									
										1
									
								
								.github/workflows/build_coverage.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -6,6 +6,7 @@ on: | ||||
|       - main | ||||
|     paths-ignore: | ||||
|       - website/** | ||||
|       - frontend/** | ||||
|       - coverage/** | ||||
| 
 | ||||
| jobs: | ||||
|  | ||||
							
								
								
									
										25
									
								
								.github/workflows/build_frontend_prs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,25 @@ | ||||
| name: PR -> Frontend Build & Test | ||||
| 
 | ||||
| on: | ||||
|   pull_request: | ||||
|     paths: | ||||
|       - frontend/** | ||||
| 
 | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: ubuntu-latest | ||||
|     defaults: | ||||
|       run: | ||||
|         working-directory: frontend | ||||
|     strategy: | ||||
|       matrix: | ||||
|         node-version: [14.x] | ||||
|     steps: | ||||
|     - uses: actions/checkout@v3 | ||||
|     - name: Use Node.js ${{ matrix.node-version }} | ||||
|       uses: actions/setup-node@v3 | ||||
|       with: | ||||
|         node-version: ${{ matrix.node-version }} | ||||
|     - run: yarn --frozen-lockfile | ||||
|     - run: yarn run test | ||||
|     - run: yarn run fmt:check | ||||
							
								
								
									
										2
									
								
								.github/workflows/build_prs.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -17,6 +17,6 @@ jobs: | ||||
|         with: | ||||
|           node-version: ${{ matrix.node-version }} | ||||
|           cache: 'yarn' | ||||
|       - run: yarn | ||||
|       - run: yarn install --frozen-lockfile --ignore-scripts | ||||
|       - run: yarn lint | ||||
|       - run: yarn build | ||||
|  | ||||
							
								
								
									
										11
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -28,3 +28,14 @@ jobs: | ||||
|           npm publish --tag ${TAG:-latest} | ||||
|         env: | ||||
|           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||||
|       - uses: aws-actions/configure-aws-credentials@v1 | ||||
|         with: | ||||
|           aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||||
|           aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||||
|           aws-region: ${{ secrets.AWS_DEFAULT_REGION }} | ||||
|       - name: Get the version | ||||
|         id: get_version | ||||
|         run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} | ||||
|       - name: Publish static assets to S3 | ||||
|         run: | | ||||
|           aws s3 cp frontend/build s3://getunleash-static/unleash/${{ steps.get_version.outputs.VERSION }} --recursive | ||||
|  | ||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -23,10 +23,6 @@ coverage/lcov-report | ||||
| # Compiled binary addons (http://nodejs.org/api/addons.html) | ||||
| build/Release | ||||
| 
 | ||||
| # webpack output | ||||
| packages/unleash-frontend/public/bundle.js | ||||
| packages/unleash-frontend/public/bundle.js.map | ||||
| 
 | ||||
| # liquibase stuff | ||||
| /sql | ||||
| unleash-db.jar | ||||
|  | ||||
| @ -7,7 +7,7 @@ WORKDIR /unleash | ||||
| 
 | ||||
| COPY . /unleash | ||||
| 
 | ||||
| RUN yarn install --frozen-lockfile --ignore-scripts && yarn run build && yarn run local:package | ||||
| RUN yarn install --frozen-lockfile && yarn run local:package | ||||
| 
 | ||||
| WORKDIR /unleash/docker | ||||
| 
 | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
|     "@passport-next/passport-google-oauth2": "^1.0.0", | ||||
|     "basic-auth": "^2.0.1", | ||||
|     "passport": "^0.6.0", | ||||
|     "unleash-server": "file:./../build/" | ||||
|     "unleash-server": "file:../build" | ||||
|   }, | ||||
|   "resolutions": { | ||||
|     "async": "^3.2.3", | ||||
|  | ||||
							
								
								
									
										799
									
								
								docker/yarn.lock
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										16
									
								
								frontend/.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,16 @@ | ||||
| # editorconfig.org | ||||
| root = true | ||||
| 
 | ||||
| [*] | ||||
| charset = utf-8 | ||||
| end_of_line = lf | ||||
| indent_style = space | ||||
| indent_size = 4 | ||||
| insert_final_newline = true | ||||
| trim_trailing_whitespace = true | ||||
| 
 | ||||
| [*.md] | ||||
| trim_trailing_whitespace = false | ||||
| 
 | ||||
| [*.json] | ||||
| indent_size = 2 | ||||
							
								
								
									
										25
									
								
								frontend/.github/workflows/e2e.feature.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,25 @@ | ||||
| name: e2e:feature | ||||
| # https://docs.github.com/en/actions/reference/events-that-trigger-workflows | ||||
| on: [deployment_status] | ||||
| jobs: | ||||
|   e2e: | ||||
|     # only runs this job on successful deploy | ||||
|     if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Dump GitHub context | ||||
|         env: | ||||
|           GITHUB_CONTEXT: ${{ toJson(github) }} | ||||
|         run: | | ||||
|           echo "$GITHUB_CONTEXT" | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|       - name: Run Cypress | ||||
|         uses: cypress-io/github-action@v2 | ||||
|         with: | ||||
|           env: AUTH_USER=admin,AUTH_PASSWORD=unleash4all | ||||
|           config: baseUrl=${{ github.event.deployment_status.target_url }} | ||||
|           record: true | ||||
|           spec: cypress/integration/feature/feature.spec.ts | ||||
|         env: | ||||
|           CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} | ||||
							
								
								
									
										25
									
								
								frontend/.github/workflows/e2e.groups.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,25 @@ | ||||
| name: e2e:groups | ||||
| # https://docs.github.com/en/actions/reference/events-that-trigger-workflows | ||||
| on: [deployment_status] | ||||
| jobs: | ||||
|   e2e: | ||||
|     # only runs this job on successful deploy | ||||
|     if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Dump GitHub context | ||||
|         env: | ||||
|           GITHUB_CONTEXT: ${{ toJson(github) }} | ||||
|         run: | | ||||
|           echo "$GITHUB_CONTEXT" | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|       - name: Run Cypress | ||||
|         uses: cypress-io/github-action@v2 | ||||
|         with: | ||||
|           env: AUTH_USER=admin,AUTH_PASSWORD=unleash4all | ||||
|           config: baseUrl=${{ github.event.deployment_status.target_url }} | ||||
|           record: true | ||||
|           spec: cypress/integration/groups/groups.spec.ts | ||||
|         env: | ||||
|           CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} | ||||
							
								
								
									
										25
									
								
								frontend/.github/workflows/e2e.project-access.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,25 @@ | ||||
| name: e2e:project-access | ||||
| # https://docs.github.com/en/actions/reference/events-that-trigger-workflows | ||||
| on: [deployment_status] | ||||
| jobs: | ||||
|   e2e: | ||||
|     # only runs this job on successful deploy | ||||
|     if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Dump GitHub context | ||||
|         env: | ||||
|           GITHUB_CONTEXT: ${{ toJson(github) }} | ||||
|         run: | | ||||
|           echo "$GITHUB_CONTEXT" | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|       - name: Run Cypress | ||||
|         uses: cypress-io/github-action@v2 | ||||
|         with: | ||||
|           env: AUTH_USER=admin,AUTH_PASSWORD=unleash4all | ||||
|           config: baseUrl=${{ github.event.deployment_status.target_url }} | ||||
|           record: true | ||||
|           spec: cypress/integration/projects/access/project-access.spec.ts | ||||
|         env: | ||||
|           CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} | ||||
							
								
								
									
										25
									
								
								frontend/.github/workflows/e2e.segments.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,25 @@ | ||||
| name: e2e:segments | ||||
| # https://docs.github.com/en/actions/reference/events-that-trigger-workflows | ||||
| on: [deployment_status] | ||||
| jobs: | ||||
|   e2e: | ||||
|     # only runs this job on successful deploy | ||||
|     if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Dump GitHub context | ||||
|         env: | ||||
|           GITHUB_CONTEXT: ${{ toJson(github) }} | ||||
|         run: | | ||||
|           echo "$GITHUB_CONTEXT" | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|       - name: Run Cypress | ||||
|         uses: cypress-io/github-action@v2 | ||||
|         with: | ||||
|           env: AUTH_USER=admin,AUTH_PASSWORD=unleash4all | ||||
|           config: baseUrl=${{ github.event.deployment_status.target_url }} | ||||
|           record: true | ||||
|           spec: cypress/integration/segments/segments.spec.ts | ||||
|         env: | ||||
|           CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} | ||||
							
								
								
									
										56
									
								
								frontend/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,56 @@ | ||||
| # Logs | ||||
| logs | ||||
| *.log | ||||
| npm-debug.log* | ||||
| 
 | ||||
| # Runtime data | ||||
| pids | ||||
| *.pid | ||||
| *.seed | ||||
| 
 | ||||
| # Directory for instrumented libs generated by jscoverage/JSCover | ||||
| lib-cov | ||||
| 
 | ||||
| # Coverage directory used by tools like istanbul | ||||
| coverage | ||||
| 
 | ||||
| # nyc test coverage | ||||
| .nyc_output | ||||
| 
 | ||||
| # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||||
| .grunt | ||||
| 
 | ||||
| # node-waf configuration | ||||
| .lock-wscript | ||||
| 
 | ||||
| # Compiled binary addons (http://nodejs.org/api/addons.html) | ||||
| build/Release | ||||
| 
 | ||||
| # Dependency directories | ||||
| node_modules | ||||
| jspm_packages | ||||
| package-lock.json | ||||
| 
 | ||||
| # Optional npm cache directory | ||||
| .npm | ||||
| 
 | ||||
| # Optional REPL history | ||||
| .node_repl_history | ||||
| 
 | ||||
| typings/ | ||||
| 
 | ||||
| # Built | ||||
| dist | ||||
| build | ||||
| 
 | ||||
| # IDE | ||||
| .idea/ | ||||
| .vscode/ | ||||
| 
 | ||||
| .DS_Store | ||||
| 
 | ||||
| cypress/downloads/* | ||||
| cypress/videos/* | ||||
| cypress/downloads/* | ||||
| cypress/screenshots/* | ||||
| .env.local | ||||
							
								
								
									
										1
									
								
								frontend/.nvmrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | ||||
| 14.20 | ||||
							
								
								
									
										3
									
								
								frontend/.prettierignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,3 @@ | ||||
| .github/* | ||||
| /src/openapi | ||||
| CHANGELOG.md | ||||
							
								
								
									
										7
									
								
								frontend/.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,7 @@ | ||||
| { | ||||
|     "singleQuote": true, | ||||
|     "bracketSpacing": true, | ||||
|     "bracketSameLine": false, | ||||
|     "arrowParens": "avoid", | ||||
|     "printWidth": 80 | ||||
| } | ||||
							
								
								
									
										68
									
								
								frontend/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,68 @@ | ||||
| # frontend | ||||
| 
 | ||||
| This directory contains the Unleash Admin UI frontend app. | ||||
| 
 | ||||
| ## Run with a local instance of the unleash-api | ||||
| 
 | ||||
| First, start the unleash-api backend on port 4242. | ||||
| Then, start the frontend dev server: | ||||
| 
 | ||||
| ``` | ||||
| cd ~/frontend | ||||
| yarn install | ||||
| yarn run start | ||||
| ``` | ||||
| 
 | ||||
| ## Run with a heroku-hosted instance of unleash-api | ||||
| 
 | ||||
| Alternatively, instead of running unleash-api on localhost, use a remote instance: | ||||
| 
 | ||||
| ``` | ||||
| cd ~/frontend | ||||
| yarn install | ||||
| yarn run start:heroku | ||||
| ``` | ||||
| 
 | ||||
| ## Running end-to-end tests | ||||
| 
 | ||||
| We have a set of Cypress tests that run on the build before a PR can be merged | ||||
| so it's important that you check these yourself before submitting a PR. | ||||
| On the server the tests will run against the deployed Heroku app so this is what you probably want to test against: | ||||
| 
 | ||||
| ``` | ||||
| yarn run start:heroku | ||||
| ``` | ||||
| 
 | ||||
| In a different shell, you can run the tests themselves: | ||||
| 
 | ||||
| ``` | ||||
| yarn run e2e:heroku | ||||
| ``` | ||||
| 
 | ||||
| If you need to test against patches against a local server instance, | ||||
| you'll need to run that, and then run the end to end tests using: | ||||
| 
 | ||||
| ``` | ||||
| yarn run e2e | ||||
| ``` | ||||
| 
 | ||||
| You may also need to test that a feature works against the enterprise version of unleash. | ||||
| Assuming the Heroku instance is still running, this can be done by: | ||||
| 
 | ||||
| ``` | ||||
| yarn run start:enterprise | ||||
| yarn run e2e | ||||
| ``` | ||||
| 
 | ||||
| ## Generating the OpenAPI client | ||||
| 
 | ||||
| The frontend uses an OpenAPI client generated from the backend's OpenAPI spec. | ||||
| Whenever there are changes to the backend API, the client should be regenerated: | ||||
| 
 | ||||
| ``` | ||||
| ./scripts/generate-openapi.sh | ||||
| ``` | ||||
| 
 | ||||
| This script assumes that you have a running instance of the enterprise backend at `http://localhost:4242`. | ||||
| The new OpenAPI client will be generated from the runtime schema of this instance. | ||||
| The target URL can be changed by setting the `UNLEASH_OPENAPI_URL` env var. | ||||
							
								
								
									
										7
									
								
								frontend/cypress.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,7 @@ | ||||
| { | ||||
|   "projectId": "tc2qff", | ||||
|   "defaultCommandTimeout": 12000, | ||||
|   "screenshotOnRunFailure": false, | ||||
|   "video": false, | ||||
|   "experimentalSessionAndOrigin": true | ||||
| } | ||||
							
								
								
									
										5
									
								
								frontend/cypress/fixtures/example.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| { | ||||
|   "name": "Using fixtures to represent data", | ||||
|   "email": "hello@cypress.io", | ||||
|   "body": "Fixtures are a great way to mock data for responses to routes" | ||||
| } | ||||
							
								
								
									
										331
									
								
								frontend/cypress/integration/feature/feature.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,331 @@ | ||||
| /// <reference types="cypress" />
 | ||||
| 
 | ||||
| export {}; | ||||
| 
 | ||||
| const ENTERPRISE = Boolean(Cypress.env('ENTERPRISE')); | ||||
| const randomId = String(Math.random()).split('.')[1]; | ||||
| const featureToggleName = `unleash-e2e-${randomId}`; | ||||
| const baseUrl = Cypress.config().baseUrl; | ||||
| const variant1 = 'variant1'; | ||||
| const variant2 = 'variant2'; | ||||
| 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', () => { | ||||
|     before(() => { | ||||
|         disableFeatureStrategiesProdGuard(); | ||||
|         disableActiveSplashScreens(); | ||||
|     }); | ||||
| 
 | ||||
|     after(() => { | ||||
|         cy.request({ | ||||
|             method: 'DELETE', | ||||
|             url: `${baseUrl}/api/admin/features/${featureToggleName}`, | ||||
|         }); | ||||
|         cy.request({ | ||||
|             method: 'DELETE', | ||||
|             url: `${baseUrl}/api/admin/archive/${featureToggleName}`, | ||||
|         }); | ||||
|     }); | ||||
| 
 | ||||
|     beforeEach(() => { | ||||
|         cy.login(); | ||||
|         cy.visit('/'); | ||||
|     }); | ||||
| 
 | ||||
|     it('can create a feature toggle', () => { | ||||
|         if (document.querySelector("[data-testid='CLOSE_SPLASH']")) { | ||||
|             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); | ||||
|     }); | ||||
| 
 | ||||
|     it('gives an error if a toggle exists with the same name', () => { | ||||
|         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.get("[data-testid='INPUT_ERROR_TEXT']").contains( | ||||
|             'A toggle with that name already exists' | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it('gives an error if a toggle name is url unsafe', () => { | ||||
|         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('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( | ||||
|             `"name" must be URL friendly` | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it('can add a gradual rollout strategy to the development environment', () => { | ||||
|         cy.visit( | ||||
|             `/projects/default/features/${featureToggleName}/strategies/create?environmentId=development&strategyName=flexibleRollout` | ||||
|         ); | ||||
| 
 | ||||
|         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', () => { | ||||
|         cy.visit( | ||||
|             `/projects/default/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(); | ||||
| 
 | ||||
|         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 variant to the feature', () => { | ||||
|         cy.visit(`/projects/default/features/${featureToggleName}/variants`); | ||||
| 
 | ||||
|         cy.intercept( | ||||
|             'PATCH', | ||||
|             `/api/admin/projects/default/features/${featureToggleName}/variants`, | ||||
|             req => { | ||||
|                 if (req.body.length === 1) { | ||||
|                     expect(req.body[0].op).to.equal('add'); | ||||
|                     expect(req.body[0].path).to.match(/\//); | ||||
|                     expect(req.body[0].value.name).to.equal(variant1); | ||||
|                 } else if (req.body.length === 2) { | ||||
|                     expect(req.body[0].op).to.equal('replace'); | ||||
|                     expect(req.body[0].path).to.match(/weight/); | ||||
|                     expect(req.body[0].value).to.equal(500); | ||||
|                     expect(req.body[1].op).to.equal('add'); | ||||
|                     expect(req.body[1].path).to.match(/\//); | ||||
|                     expect(req.body[1].value.name).to.equal(variant2); | ||||
|                 } | ||||
|             } | ||||
|         ).as('variantCreation'); | ||||
| 
 | ||||
|         cy.get('[data-testid=ADD_VARIANT_BUTTON]').click(); | ||||
|         cy.wait(1000); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variant1); | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
|         cy.wait('@variantCreation'); | ||||
|         cy.get('[data-testid=ADD_VARIANT_BUTTON]').click(); | ||||
|         cy.wait(1000); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]').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', () => { | ||||
|         cy.visit(`/projects/default/features/${featureToggleName}/variants`); | ||||
| 
 | ||||
|         cy.get(`[data-testid=VARIANT_EDIT_BUTTON_${variant1}]`).click(); | ||||
|         cy.wait(1000); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]') | ||||
|             .children() | ||||
|             .find('input') | ||||
|             .should('have.attr', 'disabled'); | ||||
|         cy.get('[data-testid=VARIANT_WEIGHT_CHECK]').find('input').check(); | ||||
|         cy.get('[data-testid=VARIANT_WEIGHT_INPUT]').clear().type('15'); | ||||
| 
 | ||||
|         cy.intercept( | ||||
|             'PATCH', | ||||
|             `/api/admin/projects/default/features/${featureToggleName}/variants`, | ||||
|             req => { | ||||
|                 expect(req.body[0].op).to.equal('replace'); | ||||
|                 expect(req.body[0].path).to.match(/weight/); | ||||
|                 expect(req.body[0].value).to.equal(850); | ||||
|                 expect(req.body[1].op).to.equal('replace'); | ||||
|                 expect(req.body[1].path).to.match(/weightType/); | ||||
|                 expect(req.body[1].value).to.equal('fix'); | ||||
|                 expect(req.body[2].op).to.equal('replace'); | ||||
|                 expect(req.body[2].path).to.match(/weight/); | ||||
|                 expect(req.body[2].value).to.equal(150); | ||||
|             } | ||||
|         ).as('variantUpdate'); | ||||
| 
 | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
|         cy.wait('@variantUpdate'); | ||||
|         cy.get(`[data-testid=VARIANT_WEIGHT_${variant1}]`).should( | ||||
|             'have.text', | ||||
|             '15 %' | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it('can delete variant', () => { | ||||
|         const variantName = 'to-be-deleted'; | ||||
| 
 | ||||
|         cy.visit(`/projects/default/features/${featureToggleName}/variants`); | ||||
|         cy.get('[data-testid=ADD_VARIANT_BUTTON]').click(); | ||||
|         cy.wait(1000); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variantName); | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
| 
 | ||||
|         cy.intercept( | ||||
|             'PATCH', | ||||
|             `/api/admin/projects/default/features/${featureToggleName}/variants`, | ||||
|             req => { | ||||
|                 const patch = req.body.find( | ||||
|                     (patch: Record<string, string>) => patch.op === 'remove' | ||||
|                 ); | ||||
|                 expect(patch.path).to.match(/\//); | ||||
|             } | ||||
|         ).as('delete'); | ||||
| 
 | ||||
|         cy.get(`[data-testid=VARIANT_DELETE_BUTTON_${variantName}]`).click(); | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
|         cy.wait('@delete'); | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										113
									
								
								frontend/cypress/integration/groups/groups.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,113 @@ | ||||
| /// <reference types="cypress" />
 | ||||
| 
 | ||||
| export {}; | ||||
| const baseUrl = Cypress.config().baseUrl; | ||||
| const randomId = String(Math.random()).split('.')[1]; | ||||
| const groupName = `unleash-e2e-${randomId}`; | ||||
| const userIds: any[] = []; | ||||
| 
 | ||||
| // Disable all active splash pages by visiting them.
 | ||||
| const disableActiveSplashScreens = () => { | ||||
|     cy.visit(`/splash/operators`); | ||||
| }; | ||||
| 
 | ||||
| describe('groups', () => { | ||||
|     before(() => { | ||||
|         disableActiveSplashScreens(); | ||||
|         cy.login(); | ||||
|         for (let i = 1; i <= 2; i++) { | ||||
|             cy.request('POST', `${baseUrl}/api/admin/user-admin`, { | ||||
|                 name: `unleash-e2e-user${i}-${randomId}`, | ||||
|                 email: `unleash-e2e-user${i}-${randomId}@test.com`, | ||||
|                 sendEmail: false, | ||||
|                 rootRole: 3, | ||||
|             }).then(response => userIds.push(response.body.id)); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     after(() => { | ||||
|         userIds.forEach(id => | ||||
|             cy.request('DELETE', `${baseUrl}/api/admin/user-admin/${id}`) | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     beforeEach(() => { | ||||
|         cy.login(); | ||||
|         cy.visit('/admin/groups'); | ||||
|         if (document.querySelector("[data-testid='CLOSE_SPLASH']")) { | ||||
|             cy.get("[data-testid='CLOSE_SPLASH']").click(); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     it('can create a group', () => { | ||||
|         cy.get("[data-testid='NAVIGATE_TO_CREATE_GROUP']").click(); | ||||
| 
 | ||||
|         cy.intercept('POST', '/api/admin/groups').as('createGroup'); | ||||
| 
 | ||||
|         cy.get("[data-testid='UG_NAME_ID']").type(groupName); | ||||
|         cy.get("[data-testid='UG_DESC_ID']").type('hello-world'); | ||||
|         cy.get("[data-testid='UG_USERS_ID']").click(); | ||||
|         cy.contains(`unleash-e2e-user1-${randomId}`).click(); | ||||
| 
 | ||||
|         cy.get("[data-testid='UG_CREATE_BTN_ID']").click(); | ||||
|         cy.wait('@createGroup'); | ||||
|         cy.contains(groupName); | ||||
|     }); | ||||
| 
 | ||||
|     it('gives an error if a group exists with the same name', () => { | ||||
|         cy.get("[data-testid='NAVIGATE_TO_CREATE_GROUP']").click(); | ||||
| 
 | ||||
|         cy.intercept('POST', '/api/admin/groups').as('createGroup'); | ||||
| 
 | ||||
|         cy.get("[data-testid='UG_NAME_ID']").type(groupName); | ||||
|         cy.get("[data-testid='INPUT_ERROR_TEXT'").contains( | ||||
|             'A group with that name already exists.' | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it('can edit a group', () => { | ||||
|         cy.contains(groupName).click(); | ||||
| 
 | ||||
|         cy.get("[data-testid='UG_EDIT_BTN_ID']").click(); | ||||
| 
 | ||||
|         cy.get("[data-testid='UG_DESC_ID']").type('-my edited description'); | ||||
| 
 | ||||
|         cy.get("[data-testid='UG_SAVE_BTN_ID']").click(); | ||||
| 
 | ||||
|         cy.contains('hello-world-my edited description'); | ||||
|     }); | ||||
| 
 | ||||
|     it('can add user to a group', () => { | ||||
|         cy.contains(groupName).click(); | ||||
| 
 | ||||
|         cy.get("[data-testid='UG_EDIT_USERS_BTN_ID']").click(); | ||||
| 
 | ||||
|         cy.get("[data-testid='UG_USERS_ID']").click(); | ||||
|         cy.contains(`unleash-e2e-user2-${randomId}`).click(); | ||||
| 
 | ||||
|         cy.get("[data-testid='UG_SAVE_BTN_ID']").click(); | ||||
| 
 | ||||
|         cy.contains(`unleash-e2e-user1-${randomId}`); | ||||
|         cy.contains(`unleash-e2e-user2-${randomId}`); | ||||
|     }); | ||||
| 
 | ||||
|     it('can remove user from a group', () => { | ||||
|         cy.contains(groupName).click(); | ||||
| 
 | ||||
|         cy.get(`[data-testid='UG_REMOVE_USER_BTN_ID-${userIds[1]}']`).click(); | ||||
| 
 | ||||
|         cy.get("[data-testid='DIALOGUE_CONFIRM_ID'").click(); | ||||
| 
 | ||||
|         cy.contains(`unleash-e2e-user1-${randomId}`); | ||||
|         cy.contains(`unleash-e2e-user2-${randomId}`).should('not.exist'); | ||||
|     }); | ||||
| 
 | ||||
|     it('can delete a group', () => { | ||||
|         cy.contains(groupName).click(); | ||||
| 
 | ||||
|         cy.get("[data-testid='UG_DELETE_BTN_ID']").click(); | ||||
|         cy.get("[data-testid='DIALOGUE_CONFIRM_ID'").click(); | ||||
| 
 | ||||
|         cy.contains(groupName).should('not.exist'); | ||||
|     }); | ||||
| }); | ||||
| @ -0,0 +1,147 @@ | ||||
| /// <reference types="cypress" />
 | ||||
| 
 | ||||
| import { | ||||
|     PA_ASSIGN_BUTTON_ID, | ||||
|     PA_ASSIGN_CREATE_ID, | ||||
|     PA_EDIT_BUTTON_ID, | ||||
|     PA_REMOVE_BUTTON_ID, | ||||
|     PA_ROLE_ID, | ||||
|     PA_USERS_GROUPS_ID, | ||||
|     PA_USERS_GROUPS_TITLE_ID, | ||||
| } from '../../../../src/utils/testIds'; | ||||
| 
 | ||||
| export {}; | ||||
| const baseUrl = Cypress.config().baseUrl; | ||||
| const randomId = String(Math.random()).split('.')[1]; | ||||
| const groupAndProjectName = `group-e2e-${randomId}`; | ||||
| const userName = `user-e2e-${randomId}`; | ||||
| const groupIds: any[] = []; | ||||
| const userIds: any[] = []; | ||||
| 
 | ||||
| // Disable all active splash pages by visiting them.
 | ||||
| const disableActiveSplashScreens = () => { | ||||
|     cy.visit(`/splash/operators`); | ||||
| }; | ||||
| 
 | ||||
| describe('project-access', () => { | ||||
|     before(() => { | ||||
|         disableActiveSplashScreens(); | ||||
|         cy.login(); | ||||
|         for (let i = 1; i <= 2; i++) { | ||||
|             const name = `${i}-${userName}`; | ||||
|             cy.request('POST', `${baseUrl}/api/admin/user-admin`, { | ||||
|                 name: name, | ||||
|                 email: `${name}@test.com`, | ||||
|                 sendEmail: false, | ||||
|                 rootRole: 3, | ||||
|             }) | ||||
|                 .as(name) | ||||
|                 .then(response => { | ||||
|                     const id = response.body.id; | ||||
|                     userIds.push(id); | ||||
|                     cy.request('POST', `${baseUrl}/api/admin/groups`, { | ||||
|                         name: `${i}-${groupAndProjectName}`, | ||||
|                         users: [{ user: { id: id } }], | ||||
|                     }).then(response => { | ||||
|                         const id = response.body.id; | ||||
|                         groupIds.push(id); | ||||
|                     }); | ||||
|                 }); | ||||
|         } | ||||
|         cy.request('POST', `${baseUrl}/api/admin/projects`, { | ||||
|             id: groupAndProjectName, | ||||
|             name: groupAndProjectName, | ||||
|         }); | ||||
|     }); | ||||
| 
 | ||||
|     after(() => { | ||||
|         userIds.forEach(id => | ||||
|             cy.request('DELETE', `${baseUrl}/api/admin/user-admin/${id}`) | ||||
|         ); | ||||
|         groupIds.forEach(id => | ||||
|             cy.request('DELETE', `${baseUrl}/api/admin/groups/${id}`) | ||||
|         ); | ||||
| 
 | ||||
|         cy.request( | ||||
|             'DELETE', | ||||
|             `${baseUrl}/api/admin/projects/${groupAndProjectName}` | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     beforeEach(() => { | ||||
|         cy.login(); | ||||
|         cy.visit(`/projects/${groupAndProjectName}/access`); | ||||
|         if (document.querySelector("[data-testid='CLOSE_SPLASH']")) { | ||||
|             cy.get("[data-testid='CLOSE_SPLASH']").click(); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     it('can assign permissions to user', () => { | ||||
|         cy.get(`[data-testid='${PA_ASSIGN_BUTTON_ID}']`).click(); | ||||
| 
 | ||||
|         cy.intercept( | ||||
|             'POST', | ||||
|             `/api/admin/projects/${groupAndProjectName}/role/4/access` | ||||
|         ).as('assignAccess'); | ||||
| 
 | ||||
|         cy.get(`[data-testid='${PA_USERS_GROUPS_ID}']`).click(); | ||||
|         cy.contains(`1-${userName}`).click(); | ||||
|         cy.get(`[data-testid='${PA_USERS_GROUPS_TITLE_ID}']`).click(); | ||||
|         cy.get(`[data-testid='${PA_ROLE_ID}']`).click(); | ||||
|         cy.contains('full control over the project').click({ force: true }); | ||||
| 
 | ||||
|         cy.get(`[data-testid='${PA_ASSIGN_CREATE_ID}']`).click(); | ||||
|         cy.wait('@assignAccess'); | ||||
|         cy.contains(`1-${userName}`); | ||||
|     }); | ||||
| 
 | ||||
|     it('can assign permissions to group', () => { | ||||
|         cy.get(`[data-testid='${PA_ASSIGN_BUTTON_ID}']`).click(); | ||||
| 
 | ||||
|         cy.intercept( | ||||
|             'POST', | ||||
|             `/api/admin/projects/${groupAndProjectName}/role/4/access` | ||||
|         ).as('assignAccess'); | ||||
| 
 | ||||
|         cy.get(`[data-testid='${PA_USERS_GROUPS_ID}']`).click(); | ||||
|         cy.contains(`1-${groupAndProjectName}`).click({ force: true }); | ||||
|         cy.get(`[data-testid='${PA_USERS_GROUPS_TITLE_ID}']`).click(); | ||||
|         cy.get(`[data-testid='${PA_ROLE_ID}']`).click(); | ||||
|         cy.contains('full control over the project').click({ force: true }); | ||||
| 
 | ||||
|         cy.get(`[data-testid='${PA_ASSIGN_CREATE_ID}']`).click(); | ||||
|         cy.wait('@assignAccess'); | ||||
|         cy.contains(`1-${groupAndProjectName}`); | ||||
|     }); | ||||
| 
 | ||||
|     it('can edit role', () => { | ||||
|         cy.get(`[data-testid='${PA_EDIT_BUTTON_ID}']`).first().click(); | ||||
| 
 | ||||
|         cy.intercept( | ||||
|             'PUT', | ||||
|             `/api/admin/projects/${groupAndProjectName}/groups/${groupIds[0]}/roles/5` | ||||
|         ).as('editAccess'); | ||||
| 
 | ||||
|         cy.get(`[data-testid='${PA_ROLE_ID}']`).click(); | ||||
|         cy.contains('within a project are allowed').click({ force: true }); | ||||
| 
 | ||||
|         cy.get(`[data-testid='${PA_ASSIGN_CREATE_ID}']`).click(); | ||||
|         cy.wait('@editAccess'); | ||||
|         cy.get("td span:contains('Owner')").should('have.length', 2); | ||||
|         cy.get("td span:contains('Member')").should('have.length', 1); | ||||
|     }); | ||||
| 
 | ||||
|     it('can remove access', () => { | ||||
|         cy.get(`[data-testid='${PA_REMOVE_BUTTON_ID}']`).first().click(); | ||||
| 
 | ||||
|         cy.intercept( | ||||
|             'DELETE', | ||||
|             `/api/admin/projects/${groupAndProjectName}/groups/${groupIds[0]}/roles/5` | ||||
|         ).as('removeAccess'); | ||||
| 
 | ||||
|         cy.contains("Yes, I'm sure").click(); | ||||
| 
 | ||||
|         cy.wait('@removeAccess'); | ||||
|         cy.contains(`1-${groupAndProjectName} has been removed from project`); | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										57
									
								
								frontend/cypress/integration/segments/segments.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,57 @@ | ||||
| /// <reference types="cypress" />
 | ||||
| 
 | ||||
| export {}; | ||||
| const randomId = String(Math.random()).split('.')[1]; | ||||
| const segmentName = `unleash-e2e-${randomId}`; | ||||
| 
 | ||||
| // Disable all active splash pages by visiting them.
 | ||||
| const disableActiveSplashScreens = () => { | ||||
|     cy.visit(`/splash/operators`); | ||||
| }; | ||||
| 
 | ||||
| describe('segments', () => { | ||||
|     before(() => { | ||||
|         disableActiveSplashScreens(); | ||||
|     }); | ||||
| 
 | ||||
|     beforeEach(() => { | ||||
|         cy.login(); | ||||
|         cy.visit('/segments'); | ||||
|     }); | ||||
| 
 | ||||
|     it('can create a segment', () => { | ||||
|         if (document.querySelector("[data-testid='CLOSE_SPLASH']")) { | ||||
|             cy.get("[data-testid='CLOSE_SPLASH']").click(); | ||||
|         } | ||||
| 
 | ||||
|         cy.get("[data-testid='NAVIGATE_TO_CREATE_SEGMENT']").click(); | ||||
| 
 | ||||
|         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); | ||||
|     }); | ||||
| 
 | ||||
|     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='SEGMENT_NAME_ID']").type(segmentName); | ||||
|         cy.get("[data-testid='SEGMENT_NEXT_BTN_ID']").should('be.disabled'); | ||||
|         cy.get("[data-testid='INPUT_ERROR_TEXT']").contains( | ||||
|             'Segment name already exists' | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it('can delete a segment', () => { | ||||
|         cy.get(`[data-testid='SEGMENT_DELETE_BTN_ID_${segmentName}']`).click(); | ||||
| 
 | ||||
|         cy.get("[data-testid='SEGMENT_DIALOG_NAME_ID']").type(segmentName); | ||||
|         cy.get("[data-testid='DIALOGUE_CONFIRM_ID'").click(); | ||||
| 
 | ||||
|         cy.contains(segmentName).should('not.exist'); | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										22
									
								
								frontend/cypress/plugins/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,22 @@ | ||||
| /// <reference types="cypress" />
 | ||||
| // ***********************************************************
 | ||||
| // This example plugins/index.js can be used to load plugins
 | ||||
| //
 | ||||
| // You can change the location of this file or turn off loading
 | ||||
| // the plugins file with the 'pluginsFile' configuration option.
 | ||||
| //
 | ||||
| // You can read more here:
 | ||||
| // https://on.cypress.io/plugins-guide
 | ||||
| // ***********************************************************
 | ||||
| 
 | ||||
| // This function is called when a project is opened or re-opened (e.g. due to
 | ||||
| // the project's config changing)
 | ||||
| 
 | ||||
| /** | ||||
|  * @type {Cypress.PluginConfig} | ||||
|  */ | ||||
| // eslint-disable-next-line no-unused-vars
 | ||||
| module.exports = (on, config) => { | ||||
|   // `on` is used to hook into various events Cypress emits
 | ||||
|   // `config` is the resolved Cypress config
 | ||||
| } | ||||
							
								
								
									
										45
									
								
								frontend/cypress/support/commands.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,45 @@ | ||||
| // ***********************************************
 | ||||
| // 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'); | ||||
| const AUTH_PASSWORD = Cypress.env('AUTH_PASSWORD'); | ||||
| 
 | ||||
| Cypress.Commands.add('login', (user = AUTH_USER, password = AUTH_PASSWORD) => | ||||
|     cy.session(user, () => { | ||||
|         cy.visit('/'); | ||||
|         cy.wait(1000); | ||||
|         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']"); | ||||
|     }) | ||||
| ); | ||||
							
								
								
									
										28
									
								
								frontend/cypress/support/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,28 @@ | ||||
| // ***********************************************************
 | ||||
| // This example support/index.js is processed and
 | ||||
| // loaded automatically before your test files.
 | ||||
| //
 | ||||
| // This is a great place to put global configuration and
 | ||||
| // behavior that modifies Cypress.
 | ||||
| //
 | ||||
| // You can change the location of this file or turn off
 | ||||
| // automatically serving support files with the
 | ||||
| // 'supportFile' configuration option.
 | ||||
| //
 | ||||
| // You can read more here:
 | ||||
| // https://on.cypress.io/configuration
 | ||||
| // ***********************************************************
 | ||||
| 
 | ||||
| // Import commands.js using ES2015 syntax:
 | ||||
| import './commands'; | ||||
| 
 | ||||
| // Alternatively you can use CommonJS syntax:
 | ||||
| // require('./commands')
 | ||||
| 
 | ||||
| declare global { | ||||
|     namespace Cypress { | ||||
|         interface Chainable { | ||||
|             login(user?: string, password?: string): Chainable<null>; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								frontend/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,23 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|     <head> | ||||
|         <meta charset="utf-8" /> | ||||
|         <link rel="icon" href="::faviconPrefix::/favicon.ico" /> | ||||
|         <meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||||
|         <meta name="baseUriPath" content="::baseUriPath::" /> | ||||
|         <meta name="cdnPrefix" content="::cdnPrefix::" /> | ||||
|         <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
|         <meta name="description" content="unleash" /> | ||||
|         <title>Unleash</title> | ||||
|         <link rel="preconnect" href="https://fonts.googleapis.com" /> | ||||
|         <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | ||||
|         <link | ||||
|             href="https://fonts.googleapis.com/css2?family=Sen:wght@400;700;800&display=swap" | ||||
|             rel="stylesheet" | ||||
|         /> | ||||
|     </head> | ||||
|     <body> | ||||
|         <div id="app"></div> | ||||
|         <script type="module" src="/src/index.tsx"></script> | ||||
|     </body> | ||||
| </html> | ||||
							
								
								
									
										9
									
								
								frontend/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,9 @@ | ||||
| require('pkginfo')(module, 'version'); | ||||
| const path = require('path'); | ||||
| 
 | ||||
| const { version } = module.exports; | ||||
| 
 | ||||
| module.exports = { | ||||
|     publicFolder: path.join(__dirname, 'build'), | ||||
|     version | ||||
| }; | ||||
							
								
								
									
										131
									
								
								frontend/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,131 @@ | ||||
| { | ||||
|   "name": "unleash-frontend-local", | ||||
|   "version": "0.0.0", | ||||
|   "private": true, | ||||
|   "files": [ | ||||
|     "index.js", | ||||
|     "build/" | ||||
|   ], | ||||
|   "engines": { | ||||
|     "node": ">=14" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "build": "tsc && vite build", | ||||
|     "lint": "eslint src --max-warnings 0", | ||||
|     "start": "vite", | ||||
|     "start:heroku": "UNLEASH_API=https://unleash.herokuapp.com yarn run start", | ||||
|     "start:enterprise": "UNLEASH_API=https://unleash4.herokuapp.com yarn run start", | ||||
|     "start:demo": "UNLEASH_BASE_PATH=/demo/ yarn start", | ||||
|     "test": "vitest run", | ||||
|     "test:watch": "vitest watch", | ||||
|     "fmt": "prettier src --write --loglevel warn", | ||||
|     "fmt:check": "prettier src --check", | ||||
|     "e2e": "yarn run cypress open --config baseUrl='http://localhost:3000' --env AUTH_USER=admin,AUTH_PASSWORD=unleash4all", | ||||
|     "e2e:heroku": "yarn run cypress open --config baseUrl='http://localhost:3000' --env AUTH_USER=example@example.com", | ||||
|     "prepare": "yarn run build" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@codemirror/lang-json": "6.0.0", | ||||
|     "@emotion/react": "11.9.3", | ||||
|     "@emotion/styled": "11.9.3", | ||||
|     "@mui/icons-material": "5.8.4", | ||||
|     "@mui/lab": "5.0.0-alpha.95", | ||||
|     "@mui/material": "5.10.1", | ||||
|     "@openapitools/openapi-generator-cli": "2.5.1", | ||||
|     "@testing-library/dom": "8.17.1", | ||||
|     "@testing-library/jest-dom": "5.16.5", | ||||
|     "@testing-library/react": "12.1.5", | ||||
|     "@testing-library/react-hooks": "7.0.2", | ||||
|     "@testing-library/user-event": "14.4.3", | ||||
|     "@types/debounce": "1.2.1", | ||||
|     "@types/deep-diff": "1.0.1", | ||||
|     "@types/jest": "28.1.7", | ||||
|     "@types/lodash.clonedeep": "4.5.7", | ||||
|     "@types/node": "17.0.18", | ||||
|     "@types/react": "17.0.48", | ||||
|     "@types/react-dom": "17.0.17", | ||||
|     "@types/react-router-dom": "5.3.3", | ||||
|     "@types/react-table": "7.7.12", | ||||
|     "@types/react-test-renderer": "17.0.2", | ||||
|     "@types/react-timeago": "4.1.3", | ||||
|     "@types/semver": "7.3.12", | ||||
|     "@uiw/react-codemirror": "4.11.5", | ||||
|     "@vitejs/plugin-react": "1.3.2", | ||||
|     "chart.js": "3.9.1", | ||||
|     "chartjs-adapter-date-fns": "2.0.0", | ||||
|     "classnames": "2.3.1", | ||||
|     "copy-to-clipboard": "3.3.2", | ||||
|     "cypress": "9.7.0", | ||||
|     "date-fns": "2.29.2", | ||||
|     "debounce": "1.2.1", | ||||
|     "deep-diff": "1.0.2", | ||||
|     "eslint": "8.22.0", | ||||
|     "eslint-config-react-app": "7.0.1", | ||||
|     "fast-json-patch": "3.1.1", | ||||
|     "http-proxy-middleware": "2.0.6", | ||||
|     "immer": "9.0.15", | ||||
|     "jsdom": "20.0.0", | ||||
|     "lodash.clonedeep": "4.5.0", | ||||
|     "msw": "0.45.0", | ||||
|     "pkginfo": "0.4.1", | ||||
|     "plausible-tracker": "0.3.8", | ||||
|     "prettier": "2.7.1", | ||||
|     "prop-types": "15.8.1", | ||||
|     "react": "17.0.2", | ||||
|     "react-chartjs-2": "4.3.1", | ||||
|     "react-dom": "17.0.2", | ||||
|     "react-hooks-global-state": "2.0.0", | ||||
|     "react-router-dom": "6.3.0", | ||||
|     "react-table": "7.8.0", | ||||
|     "react-test-renderer": "17.0.2", | ||||
|     "react-timeago": "7.1.0", | ||||
|     "sass": "1.54.5", | ||||
|     "semver": "7.3.7", | ||||
|     "swr": "1.3.0", | ||||
|     "tss-react": "3.7.1", | ||||
|     "typescript": "4.7.4", | ||||
|     "vite": "2.9.15", | ||||
|     "vite-plugin-env-compatible": "1.1.1", | ||||
|     "vite-plugin-svgr": "2.2.1", | ||||
|     "vite-tsconfig-paths": "3.5.0", | ||||
|     "vitest": "0.22.1", | ||||
|     "whatwg-fetch": "3.6.2", | ||||
|     "@uiw/codemirror-theme-duotone": "4.11.5" | ||||
|   }, | ||||
|   "jest": { | ||||
|     "moduleNameMapper": { | ||||
|       "\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/src/__mocks__/fileMock.js", | ||||
|       "\\.svg": "<rootDir>/src/__mocks__/svgMock.js", | ||||
|       "\\.(css|scss)$": "identity-obj-proxy" | ||||
|     } | ||||
|   }, | ||||
|   "browserslist": { | ||||
|     "production": [ | ||||
|       ">0.2%", | ||||
|       "not dead", | ||||
|       "not op_mini all" | ||||
|     ], | ||||
|     "development": [ | ||||
|       "last 1 chrome version", | ||||
|       "last 1 firefox version", | ||||
|       "last 1 safari version" | ||||
|     ] | ||||
|   }, | ||||
|   "eslintConfig": { | ||||
|     "extends": [ | ||||
|       "react-app", | ||||
|       "react-app/jest" | ||||
|     ], | ||||
|     "parserOptions": { | ||||
|       "warnOnUnsupportedTypeScriptVersion": false | ||||
|     }, | ||||
|     "rules": { | ||||
|       "no-restricted-globals": "off", | ||||
|       "no-useless-computed-key": "off", | ||||
|       "import/no-anonymous-default-export": "off" | ||||
|     }, | ||||
|     "ignorePatterns": [ | ||||
|       "cypress" | ||||
|     ] | ||||
|   } | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								frontend/public/cs_CZ.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/da-DK.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 645 B | 
							
								
								
									
										1
									
								
								frontend/public/datadog.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | ||||
| <svg height="2500" viewBox=".27 .27 800.01 858.98" width="2328" xmlns="http://www.w3.org/2000/svg"><path d="m670.38 608.27-71.24-46.99-59.43 99.27-69.12-20.21-60.86 92.89 3.12 29.24 330.9-60.97-19.22-206.75zm-308.59-89.14 53.09-7.3c8.59 3.86 14.57 5.33 24.87 7.95 16.04 4.18 34.61 8.19 62.11-5.67 6.4-3.17 19.73-15.36 25.12-22.31l217.52-39.46 22.19 268.56-372.65 67.16zm404.06-96.77-21.47 4.09-41.25-426.18-702.86 81.5 86.59 702.68 82.27-11.94c-6.57-9.38-16.8-20.73-34.27-35.26-24.23-20.13-15.66-54.32-1.37-75.91 18.91-36.48 116.34-82.84 110.82-141.15-1.98-21.2-5.35-48.8-25.03-67.71-.74 7.85.59 15.41.59 15.41s-8.08-10.31-12.11-24.37c-4-5.39-7.14-7.11-11.39-14.31-3.03 8.33-2.63 17.99-2.63 17.99s-6.61-15.62-7.68-28.8c-3.92 5.9-4.91 17.11-4.91 17.11s-8.59-24.62-6.63-37.88c-3.92-11.54-15.54-34.44-12.25-86.49 21.45 15.03 68.67 11.46 87.07-15.66 6.11-8.98 10.29-33.5-3.05-81.81-8.57-30.98-29.79-77.11-38.06-94.61l-.99.71c4.36 14.1 13.35 43.66 16.8 57.99 10.44 43.47 13.24 58.6 8.34 78.64-4.17 17.42-14.17 28.82-39.52 41.56-25.35 12.78-58.99-18.32-61.12-20.04-24.63-19.62-43.68-51.63-45.81-67.18-2.21-17.02 9.81-27.24 15.87-41.16-8.67 2.48-18.34 6.88-18.34 6.88s11.54-11.94 25.77-22.27c5.89-3.9 9.35-6.38 15.56-11.54-8.99-.15-16.29.11-16.29.11s14.99-8.1 30.53-14c-11.37-.5-22.25-.08-22.25-.08s33.45-14.96 59.87-25.94c18.17-7.45 35.92-5.25 45.89 9.17 13.09 18.89 26.84 29.15 55.98 35.51 17.89-7.93 23.33-12.01 45.81-18.13 19.79-21.76 35.33-24.58 35.33-24.58s-7.71 7.07-9.77 18.18c11.22-8.84 23.52-16.22 23.52-16.22s-4.76 5.88-9.2 15.22l1.03 1.53c13.09-7.85 28.48-14.04 28.48-14.04s-4.4 5.56-9.56 12.76c9.87-.08 29.89.42 37.66 1.3 45.87 1.01 55.39-48.99 72.99-55.26 22.04-7.87 31.89-12.63 69.45 24.26 32.23 31.67 57.41 88.36 44.91 101.06-10.48 10.54-31.16-4.11-54.08-32.68-12.11-15.13-21.27-33.01-25.56-55.74-3.62-19.18-17.71-30.31-17.71-30.31s8.18 18.18 8.18 34.24c0 8.77 1.1 41.56 15.16 59.96-1.39 2.69-2.04 13.31-3.58 15.34-16.36-19.77-51.49-33.92-57.22-38.09 19.39 15.89 63.96 52.39 81.08 87.37 16.19 33.08 6.65 63.4 14.84 71.25 2.33 2.25 34.82 42.73 41.07 63.07 10.9 35.45.65 72.7-13.62 95.81l-39.85 6.21c-5.83-1.62-9.76-2.43-14.99-5.46 2.88-5.1 8.61-17.82 8.67-20.44l-2.25-3.95c-12.4 17.57-33.18 34.63-50.44 44.43-22.59 12.8-48.63 10.83-65.58 5.58-48.11-14.84-93.6-47.35-104.57-55.89 0 0-.34 6.82 1.73 8.35 12.13 13.68 39.92 38.43 66.78 55.68l-57.26 6.3 27.07 210.78c-12 1.72-13.87 2.56-27.01 4.43-11.58-40.91-33.73-67.62-57.94-83.18-21.35-13.72-50.8-16.81-78.99-11.23l-1.81 2.1c19.6-2.04 42.74.8 66.51 15.85 23.33 14.75 42.13 52.85 49.05 75.79 8.86 29.32 14.99 60.68-8.86 93.92-16.97 23.63-66.51 36.69-106.53 8.44 10.69 17.19 25.14 31.25 44.59 33.9 28.88 3.92 56.29-1.09 75.16-20.46 16.11-16.56 24.65-51.19 22.4-87.66l25.49-3.7 9.2 65.46 421.98-50.81zm-256.73-177.77c-1.18 2.69-3.03 4.45-.25 13.2l.17.5.44 1.13 1.16 2.62c5.01 10.24 10.51 19.9 19.7 24.83 2.38-.4 4.84-.67 7.39-.8 8.63-.38 14.08.99 17.54 2.85.31-1.72.38-4.24.19-7.95-.67-12.97 2.57-35.03-22.36-46.64-9.41-4.37-22.61-3.02-27.01 2.43.8.1 1.52.27 2.08.46 6.65 2.33 2.14 4.62.95 7.37m69.87 121.02c-3.27-1.8-18.55-1.09-29.29.19-20.46 2.41-42.55 9.51-47.39 13.29-8.8 6.8-4.8 18.66 1.7 23.53 18.23 13.62 34.21 22.75 51.08 20.53 10.36-1.36 19.49-17.76 25.96-32.64 4.43-10.25 4.43-21.31-2.06-24.9m-181.14-104.96c5.77-5.48-28.74-12.68-55.52 5.58-19.75 13.47-20.38 42.35-1.47 58.72 1.89 1.62 3.45 2.77 4.91 3.71 5.52-2.6 11.81-5.23 19.05-7.58 12.23-3.97 22.4-6.02 30.76-7.11 4-4.47 8.65-12.34 7.49-26.59-1.58-19.33-16.23-16.26-5.22-26.73" fill="#632ca6"/></svg> | ||||
| After Width: | Height: | Size: 3.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/de_DE.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 247 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/en-GB.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/en-IN.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/en-US.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/favicon_old.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/ad.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 26 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/ae.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 494 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/af.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 30 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/ag.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/al.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/am.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 451 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/ao.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/ar.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 12 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/at.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 225 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/au.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/az.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/ba.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bb.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bd.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/be.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 683 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bf.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 247 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bh.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 879 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bi.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bj.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 332 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 265 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/br.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bs.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bt.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 40 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bw.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 523 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/by.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/bz.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 57 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/ca.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/cd.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/cf.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/cg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/ch.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 370 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/ci.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 540 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/cl.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/cm.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/cn.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/co.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 560 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/cr.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 261 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/cu.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/cv.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/cy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/cz.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/de.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 247 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/dj.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/dk.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 645 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/dm.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/do.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/dz.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/ec.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 25 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/ee.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 421 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/eg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/eh.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/er.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/es.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/et.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/fi.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 481 B | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/fj.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/public/flags-normal/fm.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.2 KiB |