Merge remote-tracking branch 'unleash-frontend-local/main' into merge-frontend-with-backend-2
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
|
1
frontend/.github/stale.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
_extends: .github
|
14
frontend/.github/workflows/add-to-project.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
name: Add new item to project board
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
uses: unleash/.github/.github/workflows/add-item-to-project.yml@main
|
||||
secrets: inherit
|
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 }}
|
23
frontend/.github/workflows/node.js.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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 install --frozen-lockfile
|
||||
- run: yarn run test
|
||||
- run: yarn run fmt:check
|
40
frontend/.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: 'Release unleash-frontend'
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Publish to npm
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- run: |
|
||||
yarn install --frozen-lockfile
|
||||
- run: |
|
||||
TAG=$(echo $GITHUB_REF_NAME | grep -oP '^v\d+\.\d+\.\d+-?\K(\w+)?')
|
||||
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 build/ s3://getunleash-static/unleash/${{ steps.get_version.outputs.VERSION }} --recursive
|
27
frontend/.github/workflows/release_changelog.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
name: 'Release changelog'
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Build changelog
|
||||
id: github_release
|
||||
uses: metcalfc/changelog-generator@v3.0.0
|
||||
with:
|
||||
myToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create release
|
||||
uses: actions/create-release@v1
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
body: ${{ steps.github_release.outputs.changelog }}
|
||||
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}}
|
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
|
||||
}
|
670
frontend/CHANGELOG.md
Normal file
@ -0,0 +1,670 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
The latest version of this document is always available in
|
||||
[releases](https://github.com/Unleash/unleash-frontend/releases).
|
||||
|
||||
# 4.2.13
|
||||
- fix: mobile percentagecircle (#502)
|
||||
- fix: status chip (#501)
|
||||
- Feat/new toggle overview (#497)
|
||||
- chore(deps): update dependency @types/node to v14.17.33
|
||||
- chore(deps): pin dependencies
|
||||
- fix: support new event format with diff will be done in th
|
||||
e UI (#496)
|
||||
- Merge pull request #495 from Unleash/fix/revive-archived-f
|
||||
eature
|
||||
- update snapshots
|
||||
- fix: disable revive feature when project is deleted
|
||||
- use useProjects to check if project deleted or not
|
||||
- fix: add correct path for create first toggle button
|
||||
- fix: remove typo from UI
|
||||
- fix: rename isProject
|
||||
Deleted to projectExists and add PermissionIconButton
|
||||
# 4.2.2
|
||||
- fix: toast text
|
||||
- Fix/strategy sidepanel (#479)
|
||||
- fix: guard for disabling envs (#4
|
||||
92)
|
||||
- fix: handle undefined project wit
|
||||
h default (#486)
|
||||
- chore(deps): update dependency cs
|
||||
s-loader to v6.5.1
|
||||
- chore(deps): update dependency @types/react-dom to v17.0.11
|
||||
- chore(deps): update dependency @types/react to v17.0.34
|
||||
- Merge pull request #483 from Unleash/fix/add-highlight-row
|
||||
|
||||
# 4.2.1
|
||||
- Feat/toggle view (#389)
|
||||
- feat: created project header (#388)
|
||||
- feat: e2e tests and mobile views (#348)
|
||||
- fix: missing-toggle link should include name-param once
|
||||
- feat: project environments configuration (#365)
|
||||
- fix: use renovater github config
|
||||
- fix: renovate should be allowed to automerge all packages
|
||||
- fix: allow renovate bot to auto-merge
|
||||
- task: remove display name from environment (#367)
|
||||
# 4.2.0
|
||||
- Feat/environment strategies (#339)
|
||||
- fix: not set env if undefined
|
||||
- Add renovate.json (#340)
|
||||
- Add project and environment scoping to API keys (#336)
|
||||
- Fix/strategy permissions (#337)
|
||||
- fix: header zIndex
|
||||
- Feat/environment crud (#335)
|
||||
# 4.1.1
|
||||
- fix: header zindex
|
||||
# 4.1.0
|
||||
- fix: sync (#334)
|
||||
- Fix/create feature (#332)
|
||||
- Fix/texture (#330)
|
||||
- fix: update constraint text field
|
||||
- Fix/minor 41 bugs (#329)
|
||||
- fix: clean up footer a bit
|
||||
- fix: content-wrapper should not take 100%
|
||||
- fix: should not show deprecated strategies
|
||||
- fix: do not filter parent routes for main nav
|
||||
- fix: add feature toggles and projects to mobile navigation (#328)
|
||||
- fix: change prepublish to prepare
|
||||
# 4.1.0-beta.4
|
||||
- Feat/feature routes
|
||||
- add enableSingleSignOut for OIDC
|
||||
- (feat/tests) feat: SSO auto-create users with default role
|
||||
# 4.1.0-beta.3
|
||||
- Fix/routing
|
||||
# 4.1.0-beta.2
|
||||
- Fix/frontend projects changes
|
||||
- chore(deps): bump path-parse from 1.0.6 to 1.0.7
|
||||
- Add switch for deciding whether to send email.
|
||||
- Fix/make sure stickiness exists
|
||||
- Load name from url
|
||||
- chore(deps): bump dns-packet from 1.3.1 to 1.3.4
|
||||
- chore(deps): bump ws from 6.2.1 to 6.2.2
|
||||
- chore(deps): bump tar from 6.1.0 to 6.1.5
|
||||
- fix: variant stickiness should not revert to default when updating
|
||||
- fix: add logout as an explicit call
|
||||
- Feat/new navigation
|
||||
# 4.0.5 fix: run use effect when value changes, not object (#315)
|
||||
- fix: run use effect when value changes, not object (#315)
|
||||
- fix: add flex wrap
|
||||
|
||||
# 4.0.5-beta.2
|
||||
- fix/pagination
|
||||
|
||||
# 4.0.5-beta.1
|
||||
- Feat/group by projects
|
||||
- fix: add missing icons
|
||||
- Offline mode
|
||||
|
||||
# 4.0.4
|
||||
|
||||
- fix: update feedback url
|
||||
|
||||
# 4.0.3
|
||||
|
||||
- fix: add null check for dueDate
|
||||
|
||||
# 4.0.2
|
||||
|
||||
- chore/update-changelog
|
||||
- feat/pnps
|
||||
- fix/customer journey patches (#304)
|
||||
- fix: add check for obscure error (#305)
|
||||
- fix: passwordchecker
|
||||
|
||||
# 4.0.1
|
||||
|
||||
- fix: Project actions need to checkAccess based on projectId
|
||||
- fix: INLINE_RUNTIME_CHUNK
|
||||
|
||||
# 4.0.0
|
||||
|
||||
- feat: update color scheme and logo (#301)
|
||||
- feat: Add admin-invoice section (#299)
|
||||
- fix: reset border radius on mobile
|
||||
- fix: standalone pages (#300)
|
||||
|
||||
# 4.0.0-beta.5
|
||||
|
||||
- chore(deps): bump hosted-git-info from 2.8.8 to 2.8.9 (#291)
|
||||
- fix: remove unused components
|
||||
- Fix/customer journey (#297)
|
||||
- feat: simple project view (#295)
|
||||
- fix: use router match util (#298)
|
||||
- fix: typo
|
||||
- fix: import correct component container (#296)
|
||||
|
||||
# 4.0.0-beta.4
|
||||
|
||||
- fix: footer
|
||||
|
||||
# 4.0.0-beta.3
|
||||
|
||||
- fix: include invite link in email (#294)
|
||||
- fix: add link to manage access to edit project
|
||||
- fix: hosted auth should not need to load initial data
|
||||
|
||||
# 4.0.0-beta.2
|
||||
|
||||
- Fix: feedback on create (#292)
|
||||
- fix: proper error handling for auth-settings being stored (#293)
|
||||
- fix: api path for auth-config
|
||||
- fix: improve password auth extra options
|
||||
|
||||
# 4.0.0-beta.1
|
||||
|
||||
- fix: support custom stickiness for flexible strategies
|
||||
- fix: add members to project use correct uri
|
||||
- Fix/console warn (#290)
|
||||
- Fix/strategy constraints (#289)
|
||||
- Set .nvmrc to 14 to reflect new requirement of node
|
||||
|
||||
# 4.0.0-beta.0
|
||||
|
||||
- feat: upgrade to node.js v14
|
||||
- fix: tiny margin for feature toggle list item
|
||||
- fix: should be allowed to create toggles without errors
|
||||
- Fix: jumping screen (#288)
|
||||
- Fix/minor changes (#285)
|
||||
- Fix/v4 corrections (#287)
|
||||
- fix: use correct baseUriPath with localStorage
|
||||
- fix: link to docs for empty apps
|
||||
|
||||
# 4.0.0-alpha.14
|
||||
|
||||
- fix: all global event log requires admin
|
||||
- fix: constraints array can be undefined
|
||||
|
||||
# 4.0.0-alpha.13
|
||||
|
||||
- feat: bootstrap endpoint for initial data (#281)
|
||||
- fix: allow permissions to be checked without project being defined (#282)
|
||||
- fix/strategy constraints (#283)
|
||||
- fix: logout should only be called once
|
||||
- fix: handle generic errors better
|
||||
|
||||
|
||||
# 4.0.0-alpha.12
|
||||
|
||||
- fix: add datadog logo for addons
|
||||
|
||||
# 4.0.0-alpha.11
|
||||
- Feat/auth hosted section (#280)
|
||||
- fix: only get legalValues if definition exists
|
||||
- Fix/variants (#278)
|
||||
- Fix/bugfixes (#279)
|
||||
- fix/locale (#277)
|
||||
- fix: added teams logo
|
||||
- Fix/cleanup (#276)
|
||||
- fix: password
|
||||
|
||||
# 4.0.0-alpha.10
|
||||
- fix: password
|
||||
|
||||
# 4.0.0-alpha.9
|
||||
- fix: optimizations
|
||||
- feat: user profile
|
||||
|
||||
# 4.0.0-alpha.8
|
||||
- chore(deps): bump ssri from 6.0.1 to 6.0.2 (#270)
|
||||
- fix: lint
|
||||
- fix: minor tuning on auth
|
||||
- feat: add new user (#273)
|
||||
|
||||
# 4.0.0-alpha.7
|
||||
- feat: add support for demo sign-in
|
||||
|
||||
# 4.0.0-alpha.5
|
||||
- fix: require ADMIN role to manage users
|
||||
- fix: add permissions for tag-types and project
|
||||
|
||||
# 4.0.0-alpha.4
|
||||
- fix: overall bugs
|
||||
- feat: user flow
|
||||
- fix: small description for toggles
|
||||
- fix: make admin pages fork for OSS and enterprise
|
||||
|
||||
# 4.0.0-alpha.3
|
||||
- fix: logout redirect logic
|
||||
- fix: redirect from login page if authorized
|
||||
- fix: material UI cleanup (#264)
|
||||
|
||||
|
||||
# 4.0.0-alpha.2
|
||||
- feat: admin users (#266)
|
||||
- fix: remove editableStrategies from useEffect deps
|
||||
- fix: Migrate to create-react-app and react-scripts (#263)
|
||||
|
||||
# 4.0.0-alpha.1
|
||||
|
||||
- fix: delete strategy
|
||||
|
||||
# 4.0.0-alpha.0
|
||||
|
||||
- feat: Switch to material-ui
|
||||
|
||||
# 3.15.0
|
||||
|
||||
- feat: Adapt API keys to new endpoint (#259)
|
||||
- chore(deps): bump elliptic from 6.5.3 to 6.5.4 (#253)
|
||||
- chore(deps): bump yargs-parser from 5.0.0 to 5.0.1 (#256)
|
||||
- chore(deps): bump y18n from 3.2.1 to 3.2.2 (#261)
|
||||
- fix: add ascending sorting (#260)
|
||||
- chore: changelog
|
||||
- fix: encode URI value when deleting tag
|
||||
- Merge pull request #257 from Unleash/fix/encode-tag-values
|
||||
- fix: encode tag value
|
||||
|
||||
# 3.14.1
|
||||
|
||||
- fix: uriencode tag.value when deleting a tag
|
||||
|
||||
# 3.14.0
|
||||
|
||||
- fix: should fetch projects once to make sure we know about projects
|
||||
- feat/rbac: edit access for projects. (#251)
|
||||
|
||||
# 3.13.5
|
||||
|
||||
- fix: check that strategies exists before calling includes
|
||||
|
||||
# 3.13.4
|
||||
|
||||
- fix: metrics invalid date
|
||||
|
||||
# 3.13.3
|
||||
|
||||
- fix: content-min-height
|
||||
|
||||
# 3.13.2
|
||||
|
||||
- feat: stale dashboard
|
||||
|
||||
# 3.13.1
|
||||
|
||||
- fix: fix update-variant-test
|
||||
- fix: unsecure => insecure
|
||||
- fix: upgrade uglifyjs-webpack-plugin to version 2.2.0
|
||||
- fix: one and only one front (#244)
|
||||
|
||||
# 3.13.0
|
||||
|
||||
- fix: minor visual for dropdowns
|
||||
- feat: add oss/enterprise version to footer (#245)
|
||||
|
||||
# 3.12.0
|
||||
|
||||
- feat: allow custom context fields to define stickiness. (#241)
|
||||
- fix: filter duplicates
|
||||
|
||||
# 3.11.4
|
||||
|
||||
- fix: should not register duplicate HTML5 backends
|
||||
|
||||
# 3.11.3
|
||||
|
||||
- fix: use findIndex when using predicate.
|
||||
|
||||
# 3.11.2
|
||||
|
||||
- fix: Add UI for showing 'create tag' errors
|
||||
- fix: UX should not eagerly store strategy updates! (#240)
|
||||
- fix: upgraded jest to version 26.6.3
|
||||
|
||||
# 3.11.1
|
||||
|
||||
- fix: make sure we also bundle SVG in public
|
||||
|
||||
# 3.11.0
|
||||
|
||||
- feat: Addon support from UI (#236)
|
||||
- fix: Use type and value from action to remove tag (#238)
|
||||
- fix: add missing space (#239)
|
||||
- fix: error in snapshot
|
||||
|
||||
# 3.10.0
|
||||
|
||||
- feat: Can now deprecate and reactivate strategies (#235)
|
||||
|
||||
# 3.9.1
|
||||
|
||||
- fix: Tags viewable on archived features (#233)
|
||||
|
||||
# 3.9.0
|
||||
|
||||
- feat: Tags for feature toggles (#232)
|
||||
- feat: Tag-types (#232)
|
||||
|
||||
# 3.8.4
|
||||
|
||||
- fix: update canisue-lite
|
||||
- fix: move all api calls to store folders
|
||||
- fix: move feature-metrics store to its own folder
|
||||
- fix: move history to folder
|
||||
- fix: move feature-toggle store into folder
|
||||
- fix: move error store into folder
|
||||
- fix: remove unused client-instance concept
|
||||
- fix: archive store in folder
|
||||
- fix: remove use of input stores
|
||||
|
||||
# 3.8.3
|
||||
|
||||
- feat: Add last seen at timestamp
|
||||
- fix: add last seen as sort option
|
||||
|
||||
# 3.8.2
|
||||
|
||||
- fix: new feature toggle gets default strategy
|
||||
|
||||
# 3.8.1
|
||||
|
||||
- fix: minor CSS improvement for strategy configs
|
||||
- fix: minor strategy configure update
|
||||
|
||||
# 3.8.0
|
||||
|
||||
- feat: Should update activation strategies immediately (#229)
|
||||
|
||||
# 3.7.0
|
||||
|
||||
- fix: remove deprecated badges
|
||||
- fix: filter for projects
|
||||
- chore(deps-dev): bump node-fetch from 2.6.0 to 2.6.1
|
||||
- feat: add technical support for projects in UI
|
||||
|
||||
# 3.6.5
|
||||
|
||||
- fix: should be possible to remove all variants.
|
||||
|
||||
# 3.6.4
|
||||
|
||||
- fix: minur ux tweaks
|
||||
|
||||
# 3.6.3
|
||||
|
||||
- fix: hide content if showing authentication modal
|
||||
- fix: add security wanring to the console
|
||||
- fix: typo description => descriptionn
|
||||
|
||||
# 3.6.2
|
||||
|
||||
- fix: show notification when app updates
|
||||
- fix: add created date for applications
|
||||
|
||||
# 3.6.1
|
||||
|
||||
- fix: minor css tweaks for mobile
|
||||
- fix: should support 409 responses as well
|
||||
|
||||
# 3.6.0
|
||||
|
||||
- feat: add search for applications
|
||||
- feat: Should be possible to remove applications
|
||||
- fix: make sure application is updated on edit
|
||||
- fix: list parameters should be trimmed
|
||||
- fix: cleanup edit application a bit
|
||||
- fix: use https url for local->heroku proxy
|
||||
- fix: upgrade whatwg-fetch to version 3.4.1
|
||||
|
||||
# 3.5.1
|
||||
|
||||
- fix: add link to all client SDKs
|
||||
- fix: use Rect.memo to increase performance
|
||||
|
||||
# [3.5.0]
|
||||
|
||||
- feat: added time-ago to toggle-list
|
||||
- feat: Add stale marking of feature toggles
|
||||
- feat: add support for toggle type (#220)
|
||||
- feat: sort by stale
|
||||
- fix: improve type-chip color
|
||||
- fix: some ux cleanup for toggle types
|
||||
|
||||
# [3.4.0]
|
||||
|
||||
- Feat: (VariantCustomization) Allow user to customize variant weights (#216)
|
||||
- bump elliptic from 6.5.2 to 6.5.3 (#218)
|
||||
- chore(deps): bump websocket-extensions from 0.1.3 to 0.1.4 (#217)
|
||||
- chore(deps-dev): bump lodash from 4.17.15 to 4.17.19 (#214)
|
||||
- fix: upgrade react-dnd to version 11.1.3
|
||||
- fix: Update react-dnd to the latest version 🚀 (#213)
|
||||
- fix: read unleash version from ui-config (#219)
|
||||
- fix: flag initial context fields
|
||||
|
||||
# [3.3.5]
|
||||
|
||||
- fix: should handle zero variants
|
||||
- fix: modal for variants
|
||||
|
||||
## [3.3.4]
|
||||
|
||||
- fix: allow overflow for strategy card
|
||||
- fix: add common component input-list-field
|
||||
|
||||
## [3.3.3]
|
||||
|
||||
- fix: improve on variant ui
|
||||
- fix: should not clear all stores on update user profile
|
||||
- fix: convert variant-view-component to function
|
||||
- fix: tune css a little
|
||||
|
||||
## [3.3.2]
|
||||
|
||||
- fix: reset stores on login/logout (#212)
|
||||
- fix: password login should prefer login options
|
||||
- fix: Transform username/password login response to json (#211)
|
||||
|
||||
## [3.3.1]
|
||||
|
||||
- feat: add support for username/password login
|
||||
- feat: locale select should be dropdown menu
|
||||
- feat: support internal routes
|
||||
- fix: adjust colors of dialog
|
||||
|
||||
## [3.2.21]
|
||||
|
||||
- fix: upgrade fetch-mock to version 9.4.0
|
||||
- fix: upgrade redux to version 4.0.5
|
||||
- fix: upgrade babel dependencies
|
||||
- fix: upgrade react-router to version 5.1.2
|
||||
- fix: upgrade react to version 16.13.1
|
||||
- fix: rename use of legacy react lifecyle methods
|
||||
- fix: upgrade react-dnd to version 10.0.2"
|
||||
|
||||
## [3.2.20]
|
||||
|
||||
- fix: logout should be real request and not just XHR
|
||||
|
||||
## [3.2.19]
|
||||
|
||||
- fix: default groupId never set for strategies (only in ui)
|
||||
|
||||
## [3.2.18]
|
||||
|
||||
- fix: clean up history view a bit
|
||||
|
||||
## [3.2.17]
|
||||
|
||||
- fix: feature search should use debounce
|
||||
- fix: footer should be on bottom
|
||||
|
||||
## [3.2.16]
|
||||
|
||||
- fix: minor improvement on context UI
|
||||
|
||||
## [3.2.15]
|
||||
|
||||
- fix: strategy config not maintainted in create toggle
|
||||
- fix: missing feature toggle should pre-fill name
|
||||
|
||||
## [3.2.14]
|
||||
|
||||
- fix: upgrade react-mdl to version 2.1.0
|
||||
|
||||
## [3.2.13]
|
||||
|
||||
- fix: Should be possible to clone even if strategy does not have groupId
|
||||
|
||||
## [3.2.12]
|
||||
|
||||
- feat: clone feature toggle configuration (#201)
|
||||
|
||||
## [3.2.11]
|
||||
|
||||
- fix: clean up variants view
|
||||
- fix: Cannot remove all variants in Admin UI
|
||||
- fix: update fetch-mock to version 8.0.0 (#199)
|
||||
- fix: update mini-css-extract-plugin to version 0.9.0
|
||||
|
||||
## [3.2.10]
|
||||
|
||||
- fix: missing strategy makes the toggle-configure crash
|
||||
|
||||
## [3.2.9]
|
||||
|
||||
- fix: Update feature toggle description. (#198)
|
||||
- fix: Update feature toggle description. (#196)
|
||||
- feat: Filter on all values in toogle data
|
||||
- feat: Add option for custom ui links (#195)
|
||||
- fix: Ensure chips are wrapped (#194)
|
||||
|
||||
## [3.2.8]
|
||||
|
||||
- fix: auto-fill groupId paramters
|
||||
- feat: Add support for flexible rollout strategy. (#193)
|
||||
|
||||
## [3.2.7]
|
||||
|
||||
- fix: upgrade react to 16.10.2
|
||||
- fix: upgrade eslint to version 6.5.1
|
||||
- fix: upgrade style-loader to version 1.0.0
|
||||
- fix: Build with node-10
|
||||
- chore: update yarn.lock
|
||||
- fix: babel-preset-env (#190)
|
||||
- fix: Added plugin to remove dist folder automatically (#191)
|
||||
- fix: Prevent text highlighting overlap between chips (#188)
|
||||
- chore: Added official sdk in the footer (#189)
|
||||
|
||||
## [3.2.6]
|
||||
|
||||
- fix: Add new locales: cz, de
|
||||
|
||||
## [3.2.5]
|
||||
|
||||
- feat: boolean strategy paramters
|
||||
|
||||
## [3.2.4]
|
||||
|
||||
- fix: Clean up the UI with empty states
|
||||
- feat: Support a few more locales
|
||||
|
||||
## [3.2.3]
|
||||
|
||||
- fix: Cleanup logut flow
|
||||
- chore: remove unleash.beta.variants flag
|
||||
|
||||
## [3.2.2]
|
||||
|
||||
- fix: Use toggle/on/off endoints to ensure correct state
|
||||
- feat: Customisable UI via config
|
||||
- chore: Update css-loader to version 2.1.1
|
||||
- chore: Update debug to version 4.1.1
|
||||
- chore: Update enzyme to latest versions
|
||||
- chore: Update redux\* to latest versions
|
||||
|
||||
## [3.2.1]
|
||||
|
||||
- fix: Fixed bug in history view preventing toggle-view.
|
||||
- feat: Add all official client SDKs to footer
|
||||
|
||||
## [3.2.0]
|
||||
|
||||
- feat: Initial beta support for variants
|
||||
- feature: Show tooltips and featuretoggle names in event view
|
||||
|
||||
## [3.1.4]
|
||||
|
||||
- feat: Add UI support for permission.
|
||||
|
||||
## [3.1.3]
|
||||
|
||||
- fix(webpack): Strip all comments in css/js bundles.
|
||||
|
||||
## [3.1.2]
|
||||
|
||||
- chore(package): update webpack to version 4.17.1
|
||||
- chore(package): move all dependencies to devDependencies as they are not used outside this module.
|
||||
|
||||
## [3.1.1]
|
||||
|
||||
- fix(strategy-create): Should be able to open the create strategy view.
|
||||
- chore(package): Upgrade redux to version 4.0.0
|
||||
|
||||
## [3.1.0]
|
||||
|
||||
- fix(react-router): Upgrade to react-router v4.
|
||||
- fix(feature-create): Default strategy should be chosen if strategy list is empty.
|
||||
- fix(feature-update): Do not change route after feature toggle update.
|
||||
|
||||
## [3.0.1]
|
||||
|
||||
- fix(feature): Create feature form inside a Card to align UI
|
||||
- feat(archive): Improve archive view, UI, search, toggle details
|
||||
- fix(navigation): signout more visible
|
||||
- fix(signout): make signout works with proxy
|
||||
- chore(package): Upgrade react to version 16.2.0
|
||||
- chore(package): update sass-loader to version 7.0.1
|
||||
|
||||
## [3.0.0]
|
||||
|
||||
- Nothing new, just locking down the version.
|
||||
|
||||
## [3.0.0-alpha.8]
|
||||
|
||||
- feat(timestamps): Make formatting of timestamps configurable.
|
||||
- fix(package): Update react-mdl to version 1.11.0
|
||||
- fix(package): update normalize.css to version 8.0.0
|
||||
|
||||
## [3.0.0-alpha.7]
|
||||
|
||||
- Move metrics poller to seperate class
|
||||
- Bugfix: CreatedAt set when creating new toggle
|
||||
- chore(lint): Added propTypes to all components
|
||||
|
||||
## [3.0.0-alpha.6]
|
||||
|
||||
- Bugfix: actions should always throw errors
|
||||
- Bugfix: filter regex should never throw.
|
||||
|
||||
## [3.0.0-alpha.5]
|
||||
|
||||
- Add support for simple builtin authentication provider
|
||||
- Add support for custom authentication provider (aka Oauth2, etc)
|
||||
|
||||
## [3.0.0-alpha.4]
|
||||
|
||||
- Added unleash-version details in footer.
|
||||
- Some house-keeping
|
||||
|
||||
## [3.0.0-alpha.2]
|
||||
|
||||
- show sdk version as part of instances details.
|
||||
- Bugfix: multiple strategies with list-inputs should work.
|
||||
|
||||
## [3.0.0-alpha.1] - 2017-06-28
|
||||
|
||||
- updated paths to use new admin api paths
|
||||
|
||||
## [2.2.0] - 2017-01-20
|
||||
|
||||
- clean filter/sorting and fabbutton #61
|
||||
- nicer fallback image for metric progress
|
||||
- fix switch width issue
|
||||
|
||||
## [2.1.0] - 2017-01-20
|
||||
|
||||
- Adjust header #51 #52
|
202
frontend/LICENSE
Normal file
@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2020 Bricks Software AS
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
68
frontend/README.md
Normal file
@ -0,0 +1,68 @@
|
||||
# unleash-frontend
|
||||
|
||||
This repo 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 unleash-frontend dev server:
|
||||
|
||||
```
|
||||
cd ~/unleash-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 ~/unleash-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
|
||||
};
|
145
frontend/package.json
Normal file
@ -0,0 +1,145 @@
|
||||
{
|
||||
"name": "unleash-frontend",
|
||||
"description": "unleash your features",
|
||||
"version": "4.14.8",
|
||||
"keywords": [
|
||||
"unleash",
|
||||
"feature toggle",
|
||||
"feature",
|
||||
"toggle"
|
||||
],
|
||||
"files": [
|
||||
"index.js",
|
||||
"build/"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "ssh://git@github.com:Unleash/unleash-frontend.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Unleash/unleash-frontend"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"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",
|
||||
"prepare": "yarn run build",
|
||||
"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",
|
||||
"isready": "yarn lint && yarn fmt && yarn prepare"
|
||||
},
|
||||
"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 |
BIN
frontend/public/flags-normal/fr.png
Normal file
After Width: | Height: | Size: 540 B |
BIN
frontend/public/flags-normal/ga.png
Normal file
After Width: | Height: | Size: 610 B |
BIN
frontend/public/flags-normal/gb.png
Normal file
After Width: | Height: | Size: 1.6 KiB |