From 87414c1c9c892a8b9c3993b4f4fc8ce34bef8067 Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Thu, 30 Sep 2021 11:44:30 +0200 Subject: [PATCH] feat: e2e tests and mobile views (#348) * fix: add sidebar button * fix: set absolute positioned sidebar button * feat: test setup * fix: add tests for adding strategy * fix: add delete strategy test * feat: add workflow * feat: add vercel token * fix: update project id * fix: increase sleep * fix: sleep * fix: vercel * fix: typo * fix: vercel preview url action * fix: yml formatting * fix: steps * fix: format * fix: runs on * fix: team id * fix: teamid * fix: add workflow * fix: remove unused import * fix: add token * fix: add configuration * fix: set env variables * fix: use with * feat: main navigation routes * feat: mobile views * fix: change spec name * fix: update cypress project id * fix: add record key * fix: button positioning * feat: permissions * fix: custom strategy * fix: remove unused action yml * fix: update yarn lock * fix: keys * fix: remove videos and screenshots * fix: add cyrpess folders to gitignore * fix: env variable --- frontend/.github/workflows/e2e.yml | 24 + frontend/.github/workflows/node.js.yml | 4 +- frontend/.gitignore | 3 + frontend/.prettierignore | 3 +- frontend/cypress.json | 3 + frontend/cypress/fixtures/example.json | 5 + .../feature-toggle/feature.spec.js | 245 ++++++++++ frontend/cypress/plugins/index.js | 22 + frontend/cypress/support/commands.js | 25 + frontend/cypress/support/index.js | 20 + frontend/package.json | 5 +- frontend/src/assets/icons/addfiles.svg | 1 + .../component/common/Dialogue/Dialogue.tsx | 2 + .../common/NoItems/NoItems.styles.ts | 32 ++ .../src/component/common/NoItems/NoItems.tsx | 16 + .../ResponsiveButton/ResponsiveButton.tsx | 4 +- frontend/src/component/common/select.tsx | 16 +- .../FeatureToggleList/FeatureToggleList.jsx | 9 +- .../list-component-test.jsx.snap | 4 +- .../FeatureStrategies.styles.ts | 6 +- .../FeatureStrategies/FeatureStrategies.tsx | 12 +- ...tureEnvironmentStrategyExecution.styles.ts | 2 +- ...ureEnvironmentStrategyExecutionWrapper.tsx | 1 + .../FeatureStrategiesConfigure.styles.ts | 4 + .../FeatureStrategiesConfigure.tsx | 25 +- .../FeatureStrategiesEnvironmentList.tsx | 32 +- .../useFeatureStrategiesEnvironmentList.ts | 6 +- .../FeatureStrategiesEnvironments.styles.ts | 32 +- .../FeatureStrategiesEnvironments.tsx | 220 ++++++--- .../FeatureStrategyEditable.styles.ts | 13 + .../FeatureStrategyEditable.tsx | 66 +-- .../FeatureStrategiesList.styles.ts | 38 ++ .../FeatureStrategiesList.tsx | 44 +- .../FeatureStrategyCard.styles.ts | 7 + .../FeatureStrategyCard.tsx | 40 +- .../FeatureStrategyAccordion.styles.ts | 14 +- .../FeatureStrategyAccordion.tsx | 32 +- .../FeatureStrategyAccordionBody.styles.ts | 6 + .../FeatureStrategyAccordionBody.tsx | 52 +- .../FeatureStrategyCreateExecution.tsx | 9 +- .../FeatureStrategyExecution.tsx | 151 +++++- .../FeatureStrategyExecutionChips.styles.ts | 3 + .../DefaultStrategy/DefaultStrategy.tsx | 12 + .../FlexibleStrategy/FlexibleStrategy.tsx | 7 + .../GeneralStrategy/GeneralStrategy.tsx | 2 +- .../common/RolloutSlider/RolloutSlider.tsx | 4 +- .../StrategyInputList/StrategyInputList.tsx | 6 + .../feature/FeatureView2/FeatureView2.tsx | 41 +- .../StrategyConstraintInputField.jsx | 2 + .../__snapshots__/routes-test.jsx.snap | 12 +- .../component/menu/__tests__/routes-test.jsx | 2 +- frontend/src/component/menu/routes.js | 13 +- .../StrategiesList/StrategiesList.jsx | 3 + .../list-component-test.jsx.snap | 1 + .../src/component/user/DemoAuth/DemoAuth.jsx | 4 +- .../user/PasswordAuth/PasswordAuth.jsx | 11 +- frontend/src/hooks/useTabs.ts | 4 +- frontend/src/interfaces/params.ts | 1 + frontend/src/interfaces/strategy.ts | 3 +- frontend/src/testIds.js | 27 ++ frontend/src/utils/get-strategy-object.ts | 19 + frontend/yarn.lock | 449 +++++++++++++++++- 62 files changed, 1635 insertions(+), 246 deletions(-) create mode 100644 frontend/.github/workflows/e2e.yml create mode 100644 frontend/cypress.json create mode 100644 frontend/cypress/fixtures/example.json create mode 100644 frontend/cypress/integration/feature-toggle/feature.spec.js create mode 100644 frontend/cypress/plugins/index.js create mode 100644 frontend/cypress/support/commands.js create mode 100644 frontend/cypress/support/index.js create mode 100644 frontend/src/assets/icons/addfiles.svg create mode 100644 frontend/src/component/common/NoItems/NoItems.styles.ts create mode 100644 frontend/src/component/common/NoItems/NoItems.tsx create mode 100644 frontend/src/component/feature/FeatureView2/FeatureStrategies/common/DefaultStrategy/DefaultStrategy.tsx create mode 100644 frontend/src/utils/get-strategy-object.ts diff --git a/frontend/.github/workflows/e2e.yml b/frontend/.github/workflows/e2e.yml new file mode 100644 index 0000000000..47a5e53e4d --- /dev/null +++ b/frontend/.github/workflows/e2e.yml @@ -0,0 +1,24 @@ +name: e2e-tests +# 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@v1 + - name: Run Cypress + uses: cypress-io/github-action@v2 + with: + env: AUTH_TOKEN=${{ secrets.UNLEASH_TOKEN }},DEFAULT_ENV="default" + config: baseUrl=${{ github.event.deployment_status.target_url }} + record: true + env: + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} \ No newline at end of file diff --git a/frontend/.github/workflows/node.js.yml b/frontend/.github/workflows/node.js.yml index 1b444ca5f5..7fe505f194 100644 --- a/frontend/.github/workflows/node.js.yml +++ b/frontend/.github/workflows/node.js.yml @@ -10,7 +10,6 @@ on: jobs: build: - runs-on: ubuntu-latest strategy: @@ -26,3 +25,6 @@ jobs: node-version: ${{ matrix.node-version }} - run: yarn - run: yarn run test + + + diff --git a/frontend/.gitignore b/frontend/.gitignore index d784e8626f..09dc7db9ed 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -48,3 +48,6 @@ build .vscode/ .DS_Store + +cypress/videos/* +cypress/screenshots/* \ No newline at end of file diff --git a/frontend/.prettierignore b/frontend/.prettierignore index 83b694704b..20ab352d5b 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -1 +1,2 @@ -CHANGELOG.md \ No newline at end of file +CHANGELOG.md +.github/* \ No newline at end of file diff --git a/frontend/cypress.json b/frontend/cypress.json new file mode 100644 index 0000000000..b6c35a0183 --- /dev/null +++ b/frontend/cypress.json @@ -0,0 +1,3 @@ +{ + "projectId": "tc2qff" +} diff --git a/frontend/cypress/fixtures/example.json b/frontend/cypress/fixtures/example.json new file mode 100644 index 0000000000..02e4254378 --- /dev/null +++ b/frontend/cypress/fixtures/example.json @@ -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" +} diff --git a/frontend/cypress/integration/feature-toggle/feature.spec.js b/frontend/cypress/integration/feature-toggle/feature.spec.js new file mode 100644 index 0000000000..e36eb442d2 --- /dev/null +++ b/frontend/cypress/integration/feature-toggle/feature.spec.js @@ -0,0 +1,245 @@ +/// + +// Welcome to Cypress! +// +// This spec file contains a variety of sample tests +// for a todo list app that are designed to demonstrate +// the power of writing tests in Cypress. +// +// To learn more about how Cypress works and +// what makes it such an awesome testing tool, +// please read our getting started guide: +// https://on.cypress.io/introduction-to-cypress + +let featureToggleName = ''; +let enterprise = false; +let strategyId = ''; +let defaultEnv = ':global:'; + +describe('feature toggle', () => { + before(() => { + featureToggleName = `unleash-e2e-${Math.floor(Math.random() * 100)}`; + enterprise = Boolean(Cypress.env('ENTERPRISE')); + + const env = Cypress.env('DEFAULT_ENV'); + if (env) { + defaultEnv = env; + } + }); + + after(() => { + const authToken = Cypress.env('AUTH_TOKEN'); + + cy.request({ + method: 'DELETE', + url: `${ + Cypress.config().baseUrl + }/api/admin/features/${featureToggleName}`, + headers: { + Authorization: authToken, + }, + }); + + cy.request({ + method: 'DELETE', + url: `${ + Cypress.config().baseUrl + }/api/admin/archive/${featureToggleName}`, + headers: { + Authorization: authToken, + }, + }); + }); + + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + const passwordAuth = Cypress.env('PASSWORD_AUTH'); + enterprise = Boolean(Cypress.env('ENTERPRISE')); + + cy.visit('/'); + + if (passwordAuth) { + cy.get('[data-test="LOGIN_EMAIL_ID"]').type('test@test.com'); + + cy.get('[data-test="LOGIN_PASSWORD_ID"]').type('qY70$NDcJNXA'); + + cy.get("[data-test='LOGIN_BUTTON']").click(); + } else { + cy.get('[data-test=LOGIN_EMAIL_ID]').type('test@unleash-e2e.com'); + cy.get('[data-test=LOGIN_BUTTON]').click(); + } + }); + + it('Creates a feature toggle', () => { + cy.get('[data-test=NAVIGATE_TO_CREATE_FEATURE').click(); + + cy.intercept('POST', '/api/admin/features').as('createFeature'); + + cy.get("[data-test='CF_NAME_ID'").type(featureToggleName); + cy.get("[data-test='CF_DESC_ID'").type('hellowrdada'); + + cy.get("[data-test='CF_CREATE_BTN_ID']").click(); + cy.wait('@createFeature'); + cy.url().should('include', featureToggleName); + }); + + it('Can add a gradual rollout strategy to the default environment', () => { + cy.wait(500); + cy.visit(`/projects/default/features2/${featureToggleName}/strategies`); + cy.get('[data-test=ADD_NEW_STRATEGY_ID]').click(); + cy.get('[data-test=ADD_NEW_STRATEGY_CARD_BUTTON_ID-1').click(); + cy.get('[data-test=ROLLOUT_SLIDER_ID') + .click() + .type('{leftarrow}'.repeat(20)); + + if (enterprise) { + cy.get('[data-test=ADD_CONSTRAINT_ID]').click(); + cy.get('[data-test=CONSTRAINT_AUTOCOMPLETE_ID]') + .type('{downArrow}'.repeat(1)) + .type('{enter}'); + cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); + } + + cy.intercept( + 'POST', + `/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/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(30); + + 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-test=ADD_NEW_STRATEGY_SAVE_ID]').first().click(); + cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); + cy.wait('@addStrategyToFeature'); + }); + + it('can update a strategy in the default environment', () => { + cy.wait(500); + cy.visit(`/projects/default/features2/${featureToggleName}/strategies`); + cy.get('[data-test=STRATEGY_ACCORDION_ID-flexibleRollout').click(); + + cy.get('[data-test=ROLLOUT_SLIDER_ID') + .first() + .click() + .type('{rightArrow}'.repeat(10)); + + cy.get('[data-test=FLEXIBLE_STRATEGY_STICKINESS_ID]') + .first() + .click() + .get('[data-test=SELECT_ITEM_ID-sessionId') + .first() + .click(); + + let newGroupId = 'new-group-id'; + cy.get('[data-test=FLEXIBLE_STRATEGY_GROUP_ID]') + .first() + .clear() + .type('new-group-id'); + + cy.intercept( + 'PUT', + `/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/strategies/${strategyId}`, + req => { + expect(req.body.parameters.groupId).to.equal(newGroupId); + expect(req.body.parameters.stickiness).to.equal('sessionId'); + expect(req.body.parameters.rollout).to.equal(60); + + 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-test=UPDATE_STRATEGY_BUTTON_ID]').first().click(); + cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); + cy.wait('@updateStrategy'); + }); + + it('can delete a strategy in the default environment', () => { + cy.wait(500); + cy.visit(`/projects/default/features2/${featureToggleName}/strategies`); + + cy.intercept( + 'DELETE', + `/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/strategies/${strategyId}`, + req => { + req.continue(res => { + expect(res.statusCode).to.equal(200); + }); + } + ).as('deleteStrategy'); + + cy.get('[data-test=DELETE_STRATEGY_ID-flexibleRollout]').click(); + cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); + cy.wait('@deleteStrategy'); + }); + + it('Can add a userid strategy to the default environment', () => { + cy.wait(500); + cy.visit(`/projects/default/features2/${featureToggleName}/strategies`); + cy.get('[data-test=ADD_NEW_STRATEGY_ID]').click(); + cy.get('[data-test=ADD_NEW_STRATEGY_CARD_BUTTON_ID-2').click(); + + if (enterprise) { + cy.get('[data-test=ADD_CONSTRAINT_ID]').click(); + cy.get('[data-test=CONSTRAINT_AUTOCOMPLETE_ID]') + .type('{downArrow}'.repeat(1)) + .type('{enter}'); + cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); + } + + cy.get('[data-test=STRATEGY_INPUT_LIST]') + .type('user1') + .type('{enter}') + .type('user2') + .type('{enter}'); + cy.get('[data-test=ADD_TO_STRATEGY_INPUT_LIST]').click(); + + cy.intercept( + 'POST', + `/api/admin/projects/default/features/${featureToggleName}/environments/${defaultEnv}/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-test=ADD_NEW_STRATEGY_SAVE_ID]').first().click(); + cy.get('[data-test=DIALOGUE_CONFIRM_ID]').click(); + cy.wait('@addStrategyToFeature'); + }); +}); diff --git a/frontend/cypress/plugins/index.js b/frontend/cypress/plugins/index.js new file mode 100644 index 0000000000..59b2bab6e4 --- /dev/null +++ b/frontend/cypress/plugins/index.js @@ -0,0 +1,22 @@ +/// +// *********************************************************** +// 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 +} diff --git a/frontend/cypress/support/commands.js b/frontend/cypress/support/commands.js new file mode 100644 index 0000000000..119ab03f7c --- /dev/null +++ b/frontend/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// 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) => { ... }) diff --git a/frontend/cypress/support/index.js b/frontend/cypress/support/index.js new file mode 100644 index 0000000000..d68db96df2 --- /dev/null +++ b/frontend/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// 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') diff --git a/frontend/package.json b/frontend/package.json index 1748b51be7..df1690906b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,9 @@ "start:heroku": "UNLEASH_API=https://unleash.herokuapp.com yarn run start", "start:ea": "UNLEASH_API=https://unleash4.herokuapp.com yarn run start", "test": "react-scripts test", - "prepare": "yarn run build" + "prepare": "yarn run build", + "e2e": "yarn run cypress open --config baseUrl='http://localhost:3000' --env PASSWORD_AUTH=true", + "e2e:enterprise": "yarn run cypress open --config baseUrl='http://localhost:3000' --env PASSWORD_AUTH=true,ENTERPRISE=true,AUTH_TOKEN=$AUTH_TOKEN" }, "devDependencies": { "@material-ui/core": "4.11.3", @@ -52,6 +54,7 @@ "classnames": "2.3.1", "craco": "0.0.3", "css-loader": "5.2.7", + "cypress": "^8.4.1", "date-fns": "2.19.0", "debounce": "1.2.1", "enzyme": "3.11.0", diff --git a/frontend/src/assets/icons/addfiles.svg b/frontend/src/assets/icons/addfiles.svg new file mode 100644 index 0000000000..b8952ba5af --- /dev/null +++ b/frontend/src/assets/icons/addfiles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/component/common/Dialogue/Dialogue.tsx b/frontend/src/component/common/Dialogue/Dialogue.tsx index 5861476069..1f8751bd58 100644 --- a/frontend/src/component/common/Dialogue/Dialogue.tsx +++ b/frontend/src/component/common/Dialogue/Dialogue.tsx @@ -9,6 +9,7 @@ import { import ConditionallyRender from '../ConditionallyRender/ConditionallyRender'; import { useStyles } from './Dialogue.styles'; +import { DIALOGUE_CONFIRM_ID } from '../../../testIds'; interface IDialogue { primaryButtonText?: string; @@ -65,6 +66,7 @@ const Dialogue: React.FC = ({ onClick={onClick} autoFocus disabled={disabledPrimaryButton} + data-test={DIALOGUE_CONFIRM_ID} > {primaryButtonText || "Yes, I'm sure"} diff --git a/frontend/src/component/common/NoItems/NoItems.styles.ts b/frontend/src/component/common/NoItems/NoItems.styles.ts new file mode 100644 index 0000000000..a3918b331d --- /dev/null +++ b/frontend/src/component/common/NoItems/NoItems.styles.ts @@ -0,0 +1,32 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(theme => ({ + container: { + display: 'flex', + width: '80%', + margin: '0 auto', + [theme.breakpoints.down(700)]: { + flexDirection: 'column', + alignItems: 'center', + }, + }, + textContainer: { + width: '50%', + [theme.breakpoints.down(700)]: { + width: '100%', + }, + }, + iconContainer: { + width: '50%', + [theme.breakpoints.down(700)]: { + width: '100%', + }, + }, + icon: { + width: '300px', + height: '200px', + [theme.breakpoints.down(700)]: { + marginTop: '2rem', + }, + }, +})); diff --git a/frontend/src/component/common/NoItems/NoItems.tsx b/frontend/src/component/common/NoItems/NoItems.tsx new file mode 100644 index 0000000000..aac3c653a1 --- /dev/null +++ b/frontend/src/component/common/NoItems/NoItems.tsx @@ -0,0 +1,16 @@ +import { ReactComponent as NoItemsIcon } from '../../../assets/icons/addfiles.svg'; +import { useStyles } from './NoItems.styles'; + +const NoItems: React.FC = ({ children }) => { + const styles = useStyles(); + return ( +
+
{children}
+
+ +
+
+ ); +}; + +export default NoItems; diff --git a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx index 86ca6f1a57..3794b5f1ba 100644 --- a/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx +++ b/frontend/src/component/common/ResponsiveButton/ResponsiveButton.tsx @@ -14,6 +14,7 @@ const ResponsiveButton: React.FC = ({ maxWidth, tooltip, children, + ...rest }) => { const smallScreen = useMediaQuery(`(max-width:${maxWidth})`); @@ -22,7 +23,7 @@ const ResponsiveButton: React.FC = ({ condition={smallScreen} show={ - + @@ -33,6 +34,7 @@ const ResponsiveButton: React.FC = ({ color="primary" variant="contained" data-loading + {...rest} > {children} diff --git a/frontend/src/component/common/select.tsx b/frontend/src/component/common/select.tsx index 4b415580cd..2c5c397134 100644 --- a/frontend/src/component/common/select.tsx +++ b/frontend/src/component/common/select.tsx @@ -1,10 +1,11 @@ import React from 'react'; import { FormControl, InputLabel, MenuItem, Select } from '@material-ui/core'; +import { SELECT_ITEM_ID } from '../../testIds'; export interface ISelectOption { key: string; title?: string; - label?: string + label?: string; } export interface ISelectMenuProps { name: string; @@ -16,8 +17,8 @@ export interface ISelectMenuProps { onChange?: ( event: React.ChangeEvent<{ name?: string; value: unknown }>, child: React.ReactNode - ) => void - disabled?: boolean + ) => void; + disabled?: boolean; className?: string; classes?: any; } @@ -36,7 +37,12 @@ const SelectMenu: React.FC = ({ }) => { const renderSelectItems = () => options.map(option => ( - + {option.label} )); @@ -62,6 +68,4 @@ const SelectMenu: React.FC = ({ ); }; - - export default SelectMenu; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx index 6d9966f241..740baab557 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleList.jsx @@ -22,6 +22,7 @@ import AccessContext from '../../../contexts/AccessContext'; import { useStyles } from './styles'; import ListPlaceholder from '../../common/ListPlaceholder/ListPlaceholder'; import { getCreateTogglePath } from '../../../utils/route-path-helpers'; +import { NAVIGATE_TO_CREATE_FEATURE } from '../../../testIds'; const FeatureToggleList = ({ fetcher, @@ -161,7 +162,9 @@ const FeatureToggleList = ({ ({ - container: { borderRadius: '10px', boxShadow: 'none', display: 'flex' }, + container: { + borderRadius: '10px', + boxShadow: 'none', + display: 'flex', + }, })); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategies.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategies.tsx index d932cad823..c3fafdafe0 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategies.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategies.tsx @@ -3,13 +3,23 @@ import FeatureStrategiesList from './FeatureStrategiesList/FeatureStrategiesList import { useStyles } from './FeatureStrategies.styles'; import FeatureStrategiesUIProvider from './FeatureStrategiesUIProvider'; import FeatureStrategiesEnvironments from './FeatureStrategiesEnvironments/FeatureStrategiesEnvironments'; +import ConditionallyRender from '../../../common/ConditionallyRender'; +import { UPDATE_FEATURE } from '../../../AccessProvider/permissions'; +import { useContext } from 'react'; +import AccessContext from '../../../../contexts/AccessContext'; const FeatureStrategies = () => { + const { hasAccess } = useContext(AccessContext); + const styles = useStyles(); return ( - + } + /> + diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureEnvironmentStrategyExecution/FeatureEnvironmentStrategyExecution.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureEnvironmentStrategyExecution/FeatureEnvironmentStrategyExecution.styles.ts index 1cb9d524ef..e36ba867fc 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureEnvironmentStrategyExecution/FeatureEnvironmentStrategyExecution.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureEnvironmentStrategyExecution/FeatureEnvironmentStrategyExecution.styles.ts @@ -5,7 +5,7 @@ export const useStyles = makeStyles(theme => ({ border: `1px solid ${theme.palette.grey[300]}`, borderRadius: '5px', width: '270px', - marginLeft: 'auto', + marginLeft: '1rem', display: 'flex', flexDirection: 'column', alignItems: 'center', diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureEnvironmentStrategyExecution/FeatureEnvironmentStrategyExecutionWrapper/FeatureEnvironmentStrategyExecutionWrapper.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureEnvironmentStrategyExecution/FeatureEnvironmentStrategyExecutionWrapper/FeatureEnvironmentStrategyExecutionWrapper.tsx index a824779b0b..59e63acc25 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureEnvironmentStrategyExecution/FeatureEnvironmentStrategyExecutionWrapper/FeatureEnvironmentStrategyExecutionWrapper.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureEnvironmentStrategyExecution/FeatureEnvironmentStrategyExecutionWrapper/FeatureEnvironmentStrategyExecutionWrapper.tsx @@ -41,6 +41,7 @@ const FeatureEnvironmentStrategyExecutionWrapper = ({ ); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesConfigure/FeatureStrategiesConfigure.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesConfigure/FeatureStrategiesConfigure.styles.ts index f831187ad0..8ab59c32a7 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesConfigure/FeatureStrategiesConfigure.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesConfigure/FeatureStrategiesConfigure.styles.ts @@ -18,9 +18,13 @@ export const useStyles = makeStyles(theme => ({ configureContainer: { display: 'flex', width: '100%' }, accordionContainer: { width: '68%', + [theme.breakpoints.down(900)]: { + width: '100%', + }, }, executionContainer: { width: '32%', + marginLeft: '1rem', }, envWarning: { marginBottom: '1rem', diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesConfigure/FeatureStrategiesConfigure.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesConfigure/FeatureStrategiesConfigure.tsx index 4482be0225..004d07840e 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesConfigure/FeatureStrategiesConfigure.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesConfigure/FeatureStrategiesConfigure.tsx @@ -1,4 +1,4 @@ -import { Button } from '@material-ui/core'; +import { Button, useMediaQuery } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import { useContext, useState } from 'react'; import { getHumanReadbleStrategyName } from '../../../../../../utils/strategy-names'; @@ -15,6 +15,7 @@ import { IFeatureViewParams } from '../../../../../../interfaces/params'; import cloneDeep from 'lodash.clonedeep'; import FeatureStrategyCreateExecution from '../../FeatureStrategyCreateExecution/FeatureStrategyCreateExecution'; import { PRODUCTION } from '../../../../../../constants/environmentTypes'; +import { ADD_NEW_STRATEGY_SAVE_ID } from '../../../../../../testIds'; interface IFeatureStrategiesConfigure { setToastData: React.Dispatch>; @@ -22,6 +23,8 @@ interface IFeatureStrategiesConfigure { const FeatureStrategiesConfigure = ({ setToastData, }: IFeatureStrategiesConfigure) => { + const smallScreen = useMediaQuery('(max-width:900px)'); + const { projectId, featureId } = useParams(); const [productionGuard, setProductionGuard] = useState(false); @@ -132,12 +135,19 @@ const FeatureStrategiesConfigure = ({ setStrategyConstraints={setStrategyConstraints} /> -
- -
+ + + + + } + />
@@ -146,6 +156,7 @@ const FeatureStrategiesConfigure = ({ color="primary" className={styles.btn} onClick={resolveSubmit} + data-test={ADD_NEW_STRATEGY_SAVE_ID} > Save diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/FeatureStrategiesEnvironmentList.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/FeatureStrategiesEnvironmentList.tsx index 6ca0e80264..b4b6935c8b 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/FeatureStrategiesEnvironmentList.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/FeatureStrategiesEnvironmentList.tsx @@ -1,11 +1,7 @@ -import { - IParameter, - IFeatureStrategy, -} from '../../../../../../interfaces/strategy'; +import { IFeatureStrategy } from '../../../../../../interfaces/strategy'; import { FEATURE_STRATEGIES_DRAG_TYPE } from '../../FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard'; import { DropTargetMonitor, useDrop } from 'react-dnd'; import { Fragment } from 'react'; -import { resolveDefaultParamValue } from '../../../../strategy/AddStrategy/utils'; import useStrategies from '../../../../../../hooks/api/getters/useStrategies/useStrategies'; import { useStyles } from './FeatureStrategiesEnvironmentList.styles'; import classnames from 'classnames'; @@ -18,6 +14,7 @@ import useDeleteStrategyMarkup from './useDeleteStrategyMarkup'; import useProductionGuardMarkup from './useProductionGuardMarkup'; import FeatureStrategyEditable from '../FeatureStrategyEditable/FeatureStrategyEditable'; import { PRODUCTION } from '../../../../../../constants/environmentTypes'; +import { getStrategyObject } from '../../../../../../utils/get-strategy-object'; interface IFeatureStrategiesEnvironmentListProps { strategies: IFeatureStrategy[]; @@ -57,12 +54,12 @@ const FeatureStrategiesEnvironmentList = ({ }; }, drop(item: IFeatureDragItem, monitor: DropTargetMonitor) { - // const dragIndex = item.index; - // const hoverIndex = index; - - const strategy = selectStrategy(item.name); + const strategy = getStrategyObject( + selectableStrategies, + item.name, + featureId + ); if (!strategy) return; - //addNewStrategy(strategy); setConfigureNewStrategy(strategy); setExpandedSidebar(false); }, @@ -97,19 +94,6 @@ const FeatureStrategiesEnvironmentList = ({ updateStrategy(strategy); }; - const selectStrategy = (name: string) => { - const selectedStrategy = selectableStrategies.find( - strategy => strategy.name === name - ); - const parameters = {} as IParameter; - - selectedStrategy?.parameters.forEach(({ name }: IParameter) => { - parameters[name] = resolveDefaultParamValue(name, featureId); - }); - - return { name, parameters, constraints: [] }; - }; - const renderStrategies = () => { return strategies.map((strategy, index) => { if (index !== strategies.length - 1) { @@ -119,6 +103,7 @@ const FeatureStrategiesEnvironmentList = ({ currentStrategy={strategy} setDelDialog={setDelDialog} updateStrategy={resolveUpdateStrategy} + index={index} /> @@ -131,6 +116,7 @@ const FeatureStrategiesEnvironmentList = ({ setDelDialog={setDelDialog} currentStrategy={strategy} updateStrategy={resolveUpdateStrategy} + index={index} /> ); } diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/useFeatureStrategiesEnvironmentList.ts b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/useFeatureStrategiesEnvironmentList.ts index 8e2ac11264..13a02fe35d 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/useFeatureStrategiesEnvironmentList.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironmentList/useFeatureStrategiesEnvironmentList.ts @@ -7,7 +7,9 @@ import { IFeatureViewParams } from '../../../../../../interfaces/params'; import { IFeatureStrategy } from '../../../../../../interfaces/strategy'; import cloneDeep from 'lodash.clonedeep'; -const useFeatureStrategiesEnvironmentList = (strategies: IFeatureStrategy[]) => { +const useFeatureStrategiesEnvironmentList = ( + strategies: IFeatureStrategy[] +) => { const { projectId, featureId } = useParams(); const { deleteStrategyFromFeature, updateStrategyOnFeature } = @@ -46,7 +48,7 @@ const useFeatureStrategiesEnvironmentList = (strategies: IFeatureStrategy[]) => await updateStrategyOnFeature( projectId, featureId, - activeEnvironment.id, + activeEnvironment.name, updatedStrategy.id, updateStrategyPayload ); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.styles.ts index 6e0a6e5d7c..fcef882b64 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.styles.ts @@ -3,22 +3,45 @@ import { makeStyles } from '@material-ui/core/styles'; export const useStyles = makeStyles(theme => ({ container: { width: '70%', + [theme.breakpoints.down(900)]: { + width: '50%', + }, + [theme.breakpoints.down(700)]: { + width: '0%', + }, }, fullWidth: { width: '90%', + [theme.breakpoints.down(700)]: { + width: '85%', + }, }, environmentsHeader: { padding: '2rem 2rem 1rem 2rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center', + [theme.breakpoints.down(700)]: { + padding: '1.5rem', + }, }, tabContentContainer: { padding: '1rem 2rem 2rem 2rem', display: 'flex', justifyContent: 'space-between', + [theme.breakpoints.down(700)]: { + padding: '1.5rem', + }, + }, + listContainerWithoutSidebar: { + width: '100%', + }, + listContainer: { + width: '70%', + [theme.breakpoints.down(700)]: { + width: '100%', + }, }, - listContainer: { width: '70%' }, listContainerFullWidth: { width: '100%' }, containerListView: { display: 'none', @@ -41,4 +64,11 @@ export const useStyles = makeStyles(theme => ({ textTransform: 'none', width: 'auto', }, + noItemsParagraph: { + margin: '1rem 0', + }, + link: { + display: 'block', + margin: '1rem 0 0 0', + }, })); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx index b31c1e65ad..4cb213451a 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx @@ -1,7 +1,7 @@ import { useParams } from 'react-router-dom'; import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature'; import { useStyles } from './FeatureStrategiesEnvironments.styles'; -import { Tabs, Tab, Button } from '@material-ui/core'; +import { Tabs, Tab, Button, useMediaQuery } from '@material-ui/core'; import TabPanel from '../../../../common/TabNav/TabPanel'; import useTabs from '../../../../../hooks/useTabs'; import FeatureStrategiesEnvironmentList from './FeatureStrategiesEnvironmentList/FeatureStrategiesEnvironmentList'; @@ -15,15 +15,24 @@ import { IFeatureViewParams } from '../../../../../interfaces/params'; import cloneDeep from 'lodash.clonedeep'; import FeatureStrategiesRefresh from './FeatureStrategiesRefresh/FeatureStrategiesRefresh'; import FeatureEnvironmentStrategyExecution from './FeatureEnvironmentStrategyExecution/FeatureEnvironmentStrategyExecution'; +import { ADD_NEW_STRATEGY_ID } from '../../../../../testIds'; +import NoItems from '../../../../common/NoItems/NoItems'; +import ResponsiveButton from '../../../../common/ResponsiveButton/ResponsiveButton'; +import { Add } from '@material-ui/icons'; +import AccessContext from '../../../../../contexts/AccessContext'; +import { UPDATE_FEATURE } from '../../../../AccessProvider/permissions'; const FeatureStrategiesEnvironments = () => { + const smallScreen = useMediaQuery('(max-width:700px)'); + const { hasAccess } = useContext(AccessContext); + const startingTabId = 0; const { projectId, featureId } = useParams(); const { toast, setToastData } = useToast(); const [showRefreshPrompt, setShowRefreshPrompt] = useState(false); const styles = useStyles(); - const { a11yProps, activeTab, setActiveTab } = useTabs(startingTabId); + const { a11yProps, activeTabIdx, setActiveTab } = useTabs(startingTabId); const { setActiveEnvironment, configureNewStrategy, @@ -62,7 +71,8 @@ const FeatureStrategiesEnvironments = () => { }, [feature]); useEffect(() => { - setActiveEnvironment(feature?.environments[activeTab]); + if (!feature?.environments?.length > 0) return; + setActiveEnvironment(feature?.environments[activeTabIdx]); /* eslint-disable-next-line */ }, [feature]); @@ -91,8 +101,8 @@ const FeatureStrategiesEnvironments = () => { equal = false; } - feature.environments.forEach(env => { - const cachedEnv = featureCache.environments.find( + feature?.environments?.forEach(env => { + const cachedEnv = featureCache?.environments?.find( cacheEnv => cacheEnv.name === env.name ); @@ -100,8 +110,13 @@ const FeatureStrategiesEnvironments = () => { equal = false; return; } + // If displayName is different + if (env?.displayName !== cachedEnv?.displayName) { + equal = false; + return; + } // If the type of environments are different - if (env.type !== cachedEnv.type) { + if (env?.type !== cachedEnv?.type) { equal = false; return; } @@ -109,8 +124,8 @@ const FeatureStrategiesEnvironments = () => { if (!equal) return equal; - feature.environments.forEach(env => { - const cachedEnv = featureCache.environments.find( + feature?.environments?.forEach(env => { + const cachedEnv = featureCache?.environments?.find( cachedEnv => cachedEnv.name === env.name ); @@ -121,8 +136,8 @@ const FeatureStrategiesEnvironments = () => { return; } - env.strategies.forEach(strategy => { - const cachedStrategy = cachedEnv.strategies.find( + env?.strategies?.forEach(strategy => { + const cachedStrategy = cachedEnv?.strategies?.find( cachedStrategy => cachedStrategy.id === strategy.id ); // Check stickiness @@ -199,29 +214,94 @@ const FeatureStrategiesEnvironments = () => { const listContainerClasses = classNames(styles.listContainer, { [styles.listContainerFullWidth]: expandedSidebar, + [styles.listContainerWithoutSidebar]: !hasAccess(UPDATE_FEATURE), }); return featureCache?.environments?.map((env, index) => { return (
-
- -
0 || expandedSidebar } show={ - +
+ +
+ + } + /> + + } + elseShow={ + +

+ No strategies added in the{' '} + {env.name} environment +

+ +

+ Strategies added in this + environment will only be + executed if the SDK is using an + API key configured for this + environment. + + Read more here + +

+ + setExpandedSidebar( + prev => !prev + ) + } + > + Add your first strategy + + } + /> + + } /> } /> @@ -246,49 +326,67 @@ const FeatureStrategiesEnvironments = () => { return (
-
-

Environments

+ +
+

Environments

- - -
-
- { - setActiveTab(tabId); - setActiveEnvironment(featureCache?.environments[tabId]); - }} - indicatorColor="primary" - textColor="primary" - className={styles.tabNavigation} - > - {renderTabs()} - -
+ + + setExpandedSidebar(prev => !prev) + } + Icon={Add} + maxWidth="700px" + disabled={!hasAccess(UPDATE_FEATURE)} + > + Add new strategy + + } + /> +
+
+ { + setActiveTab(tabId); + setActiveEnvironment( + featureCache?.environments[tabId] + ); + }} + indicatorColor="primary" + textColor="primary" + className={styles.tabNavigation} + > + {renderTabs()} + +
-
- {renderTabPanels()} - - } - /> -
- {toast} +
+ {renderTabPanels()} + + } + /> +
+ {toast} + + } + />
); }; diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.styles.ts index 1936a6ce5d..0c86b52a17 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.styles.ts @@ -14,10 +14,23 @@ export const useStyles = makeStyles(theme => ({ borderRadius: '3px', fontSize: theme.fontSizes.smallerBody, zIndex: 400, + [theme.breakpoints.down(500)]: { + right: 100, + }, }, buttonContainer: { display: 'flex', alignItems: 'center', marginTop: '1rem', + [theme.breakpoints.down(500)]: { + flexDirection: 'column', + }, + }, + editButton: { + margin: '0 1rem 0 0', + [theme.breakpoints.down(500)]: { + width: '100%', + margin: '0.4rem 0', + }, }, })); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx index 3e786587b7..224958a91d 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategyEditable/FeatureStrategyEditable.tsx @@ -14,20 +14,31 @@ import cloneDeep from 'lodash.clonedeep'; import { Button, IconButton, Tooltip } from '@material-ui/core'; import ConditionallyRender from '../../../../../common/ConditionallyRender'; import { useStyles } from './FeatureStrategyEditable.styles'; -import { Delete, FileCopy } from '@material-ui/icons'; +import { Delete } from '@material-ui/icons'; import { PRODUCTION } from '../../../../../../constants/environmentTypes'; +import { + DELETE_STRATEGY_ID, + STRATEGY_ACCORDION_ID, + UPDATE_STRATEGY_BUTTON_ID, +} from '../../../../../../testIds'; +import AccessContext from '../../../../../../contexts/AccessContext'; +import { UPDATE_FEATURE } from '../../../../../AccessProvider/permissions'; interface IFeatureStrategyEditable { currentStrategy: IFeatureStrategy; setDelDialog?: React.Dispatch>; updateStrategy: (strategy: IFeatureStrategy) => void; + index?: number; } const FeatureStrategyEditable = ({ currentStrategy, updateStrategy, setDelDialog, + index, }: IFeatureStrategyEditable) => { + const { hasAccess } = useContext(AccessContext); + const { projectId, featureId } = useParams(); const { activeEnvironment, featureCache, dirty, setDirty } = useContext( FeatureStrategiesUIContext @@ -122,36 +133,31 @@ const FeatureStrategyEditable = ({ - - { - e.stopPropagation(); - setDelDialog({ - strategyId: strategy.id, - show: true, - }); - }} - > - - - - - - { - e.stopPropagation(); - }} - > - - - - + + { + e.stopPropagation(); + setDelDialog({ + strategyId: strategy.id, + show: true, + }); + }} + > + + + + } + /> } > Save changes -
diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.styles.ts index 0e2fd1c6de..d7ce11ef79 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.styles.ts @@ -6,8 +6,46 @@ export const useStyles = makeStyles(theme => ({ padding: '2rem', borderRight: `1px solid ${theme.palette.grey[300]}`, transition: 'width 0.3s ease', + position: 'relative', + minHeight: '400px', + [theme.breakpoints.down(900)]: { + width: '50%', + }, + [theme.breakpoints.down(700)]: { + padding: '1rem', + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }, }, sidebarSmall: { width: '10%', + [theme.breakpoints.down(700)]: { + width: '15%', + }, + }, + iconButton: { + position: 'absolute', + top: '300px', + right: '-25px', + backgroundColor: theme.palette.grey[300], + [theme.breakpoints.down(700)]: { + right: '-10px', + }, + }, + icon: { + transition: 'transform 0.4s ease', + transitionDelay: '0.4s', + }, + expandedIcon: { + transform: 'rotate(180deg)', + }, + mobileNavContainer: { + display: 'flex', + alignItems: 'center', + width: '100%', + justifyContent: 'space-between', + padding: '1rem', }, })); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.tsx index 27413d710f..39dfa4f4bb 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategiesList.tsx @@ -5,31 +5,67 @@ import { useStyles } from './FeatureStrategiesList.styles'; import { useContext } from 'react'; import FeatureStrategiesUIContext from '../../../../../contexts/FeatureStrategiesUIContext'; import classnames from 'classnames'; +import { Button, IconButton, useMediaQuery } from '@material-ui/core'; +import { DoubleArrow } from '@material-ui/icons'; +import ConditionallyRender from '../../../../common/ConditionallyRender'; const FeatureStrategiesList = () => { - const { expandedSidebar } = useContext(FeatureStrategiesUIContext); + const smallScreen = useMediaQuery('(max-width:700px)'); + const { expandedSidebar, setExpandedSidebar } = useContext( + FeatureStrategiesUIContext + ); const styles = useStyles(); const { strategies } = useStrategies(); + const DEFAULT_STRATEGY = 'default'; + const renderStrategies = () => { return strategies - .filter((strategy: IStrategy) => !strategy.deprecated) - .map((strategy: IStrategy) => ( + .filter( + (strategy: IStrategy) => + !strategy.deprecated && strategy.name !== DEFAULT_STRATEGY + ) + .map((strategy: IStrategy, index: number) => ( )); }; + const toggleSidebar = () => { + setExpandedSidebar(prev => !prev); + }; + const classes = classnames(styles.sidebar, { [styles.sidebarSmall]: !expandedSidebar, }); - return
{renderStrategies()}
; + const iconClasses = classnames(styles.icon, { + [styles.expandedIcon]: expandedSidebar, + }); + + return ( +
+ +

Select strategy

+ +
+ } + /> + + + + {renderStrategies()} + + ); }; export default FeatureStrategiesList; diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.styles.ts index a9263f6f9f..f61fd80e31 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.styles.ts @@ -8,6 +8,8 @@ export const useStyles = makeStyles(theme => ({ borderRadius: '3px', margin: '0.5rem 0', display: 'flex', + position: 'relative', + width: '100%', '&:active': { backgroundColor: theme.palette.primary.main, color: '#fff', @@ -44,4 +46,9 @@ export const useStyles = makeStyles(theme => ({ isDragging: { backgroundColor: theme.palette.primary.main, }, + addButton: { + position: 'absolute', + right: 0, + top: 0, + }, })); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.tsx index 79d7d1755a..aea069b6a7 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesList/FeatureStrategyCard/FeatureStrategyCard.tsx @@ -1,7 +1,18 @@ import { IconButton, Tooltip } from '@material-ui/core'; +import { Add } from '@material-ui/icons'; import classNames from 'classnames'; +import { useContext } from 'react'; import { useDrag } from 'react-dnd'; -import { getFeatureStrategyIcon, getHumanReadbleStrategyName } from '../../../../../../utils/strategy-names'; +import { useParams } from 'react-router-dom'; +import FeatureStrategiesUIContext from '../../../../../../contexts/FeatureStrategiesUIContext'; +import useStrategies from '../../../../../../hooks/api/getters/useStrategies/useStrategies'; +import { IFeatureViewParams } from '../../../../../../interfaces/params'; +import { ADD_NEW_STRATEGY_CARD_BUTTON_ID } from '../../../../../../testIds'; +import { getStrategyObject } from '../../../../../../utils/get-strategy-object'; +import { + getFeatureStrategyIcon, + getHumanReadbleStrategyName, +} from '../../../../../../utils/strategy-names'; import ConditionallyRender from '../../../../../common/ConditionallyRender'; import { useStyles } from './FeatureStrategyCard.styles'; @@ -9,6 +20,7 @@ interface IFeatureStrategyCardProps { name: string; description: string; configureNewStrategy: boolean; + index?: number; } export const FEATURE_STRATEGIES_DRAG_TYPE = 'FEATURE_STRATEGIES_DRAG_TYPE'; @@ -17,11 +29,26 @@ const FeatureStrategyCard = ({ name, description, configureNewStrategy, + index, }: IFeatureStrategyCardProps) => { + const { featureId } = useParams(); + const { strategies } = useStrategies(); + + const { setConfigureNewStrategy, setExpandedSidebar } = useContext( + FeatureStrategiesUIContext + ); + + const handleClick = () => { + const strategy = getStrategyObject(strategies, name, featureId); + if (!strategy) return; + setConfigureNewStrategy(strategy); + setExpandedSidebar(false); + }; + const styles = useStyles(); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [ _ , drag] = useDrag({ + const [_, drag] = useDrag({ type: FEATURE_STRATEGIES_DRAG_TYPE, item: () => { return { name }; @@ -48,6 +75,15 @@ const FeatureStrategyCard = ({
+ + +

{readableName}

diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.styles.ts index 856a913a00..7d98dc58a1 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.styles.ts @@ -27,9 +27,14 @@ export const useStyles = makeStyles(theme => ({ width: '100%', }, accordionHeader: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', + maxWidth: '200px', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + overflow: 'hidden', + [theme.breakpoints.down(700)]: { + maxWidth: '100px', + fontSize: theme.fontSizes.smallBody, + }, }, accordionActions: { marginLeft: 'auto', @@ -43,4 +48,7 @@ export const useStyles = makeStyles(theme => ({ fontSize: theme.fontSizes.smallBody, marginLeft: '0.5rem', }, + accordionDetails: { + width: '100%', + }, })); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.tsx index 2e1a7aa707..a6b222a65a 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordion.tsx @@ -1,9 +1,20 @@ -import { IConstraint, IParameter, IFeatureStrategy } from '../../../../../interfaces/strategy'; +import { + IConstraint, + IParameter, + IFeatureStrategy, +} from '../../../../../interfaces/strategy'; import Accordion from '@material-ui/core/Accordion'; -import { AccordionDetails, AccordionSummary } from '@material-ui/core'; +import { + AccordionDetails, + AccordionSummary, + useMediaQuery, +} from '@material-ui/core'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import { getFeatureStrategyIcon, getHumanReadbleStrategyName } from '../../../../../utils/strategy-names'; +import { + getFeatureStrategyIcon, + getHumanReadbleStrategyName, +} from '../../../../../utils/strategy-names'; import { useStyles } from './FeatureStrategyAccordion.styles'; import ConditionallyRender from '../../../../common/ConditionallyRender'; import FeatureStrategyAccordionBody from './FeatureStrategyAccordionBody/FeatureStrategyAccordionBody'; @@ -29,7 +40,9 @@ const FeatureStrategyAccordion: React.FC = ({ setStrategyConstraints, actions, children, + ...rest }) => { + const smallScreen = useMediaQuery('(max-width:500px)'); const styles = useStyles(); const strategyName = getHumanReadbleStrategyName(strategy.name); const Icon = getFeatureStrategyIcon(strategy.name); @@ -43,7 +56,7 @@ const FeatureStrategyAccordion: React.FC = ({ }; return ( -
+
} @@ -51,12 +64,13 @@ const FeatureStrategyAccordion: React.FC = ({ id={strategy.name} >
-

- {strategyName} -

+ +

{strategyName}

Rolling out to {parameters?.rollout}% @@ -67,7 +81,7 @@ const FeatureStrategyAccordion: React.FC = ({
{actions}
- + ({ fontWeight: 'normal', marginBottom: '0.5rem', }, + accordionContainer: { + width: '80%', + [theme.breakpoints.down(800)]: { + width: '100%', + }, + }, constraintHeader: { fontWeight: 'bold', fontSize: theme.fontSizes.smallBody, diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx index 0a9dba7a23..3022ec5a1e 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyAccordion/FeatureStrategyAccordionBody/FeatureStrategyAccordionBody.tsx @@ -1,12 +1,14 @@ -import DefaultStrategy from '../../../../strategy/EditStrategyModal/default-strategy'; import FlexibleStrategy from '../../common/FlexibleStrategy/FlexibleStrategy'; -import { IConstraint, IFeatureStrategy } from '../../../../../../interfaces/strategy'; +import { + IConstraint, + IFeatureStrategy, +} from '../../../../../../interfaces/strategy'; import useUnleashContext from '../../../../../../hooks/api/getters/useUnleashContext/useUnleashContext'; import useStrategies from '../../../../../../hooks/api/getters/useStrategies/useStrategies'; import GeneralStrategy from '../../common/GeneralStrategy/GeneralStrategy'; import UserWithIdStrategy from '../../common/UserWithIdStrategy/UserWithId'; import StrategyConstraints from '../../common/StrategyConstraints/StrategyConstraints'; -import { useState } from 'react'; +import { useContext, useState } from 'react'; import ConditionallyRender from '../../../../../common/ConditionallyRender'; import useUiConfig from '../../../../../../hooks/api/getters/useUiConfig/useUiConfig'; import { C } from '../../../../../common/flags'; @@ -14,6 +16,10 @@ import { Button } from '@material-ui/core'; import { useStyles } from './FeatureStrategyAccordionBody.styles'; import Dialogue from '../../../../../common/Dialogue'; import FeatureStrategiesSeparator from '../../FeatureStrategiesEnvironments/FeatureStrategiesSeparator/FeatureStrategiesSeparator'; +import DefaultStrategy from '../../common/DefaultStrategy/DefaultStrategy'; +import { ADD_CONSTRAINT_ID } from '../../../../../../testIds'; +import AccessContext from '../../../../../../contexts/AccessContext'; +import { UPDATE_FEATURE } from '../../../../../AccessProvider/permissions'; interface IFeatureStrategyAccordionBodyProps { strategy: IFeatureStrategy; @@ -38,6 +44,7 @@ const FeatureStrategyAccordionBody: React.FC const { strategies } = useStrategies(); const { uiConfig } = useUiConfig(); const [showConstraints, setShowConstraints] = useState(false); + const { hasAccess } = useContext(AccessContext); const { context } = useUnleashContext(); @@ -131,7 +138,7 @@ const FeatureStrategyAccordionBody: React.FC const ON = uiConfig.flags[C]; return ( -
+
Constraints

{renderConstraints()} - + + + Add constraint + + } + /> } /> @@ -167,13 +180,20 @@ const FeatureStrategyAccordionBody: React.FC setConstraintError={setConstraintError} /> - + } /> + {children}
); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyCreateExecution/FeatureStrategyCreateExecution.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyCreateExecution/FeatureStrategyCreateExecution.tsx index 42de8ec688..09ebea99f2 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyCreateExecution/FeatureStrategyCreateExecution.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyCreateExecution/FeatureStrategyCreateExecution.tsx @@ -1,15 +1,21 @@ -import { IConstraint, IParameter } from '../../../../../interfaces/strategy'; +import { + IConstraint, + IFeatureStrategy, + IParameter, +} from '../../../../../interfaces/strategy'; import FeatureStrategyExecution from '../FeatureStrategyExecution/FeatureStrategyExecution'; import { useStyles } from './FeatureStrategyCreateExecution.styles'; interface IFeatureStrategyCreateExecutionProps { parameters: IParameter; constraints: IConstraint[]; + configureNewStrategy: IFeatureStrategy; } const FeatureStrategyCreateExecution = ({ parameters, constraints, + configureNewStrategy, }: IFeatureStrategyCreateExecutionProps) => { const styles = useStyles(); return ( @@ -18,6 +24,7 @@ const FeatureStrategyCreateExecution = ({
); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyExecution/FeatureStrategyExecution.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyExecution/FeatureStrategyExecution.tsx index 7708fb17bd..9f1e5938a6 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyExecution/FeatureStrategyExecution.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyExecution/FeatureStrategyExecution.tsx @@ -1,25 +1,37 @@ import { Fragment } from 'react'; -import { IConstraint, IParameter } from '../../../../../interfaces/strategy'; +import { + IConstraint, + IFeatureStrategy, + IParameter, +} from '../../../../../interfaces/strategy'; import ConditionallyRender from '../../../../common/ConditionallyRender'; import PercentageCircle from '../../../../common/PercentageCircle/PercentageCircle'; import FeatureStrategiesSeparator from '../FeatureStrategiesEnvironments/FeatureStrategiesSeparator/FeatureStrategiesSeparator'; import { useStyles } from './FeatureStrategyExecution.styles'; import FeatureStrategyExecutionConstraint from './FeatureStrategyExecutionConstraint/FeatureStrategyExecutionConstraint'; import FeatureStrategyExecutionChips from './FeatureStrategyExecutionChips/FeatureStrategyExecutionChips'; +import useStrategies from '../../../../../hooks/api/getters/useStrategies/useStrategies'; interface IFeatureStrategiesExecutionProps { parameters: IParameter; constraints?: IConstraint[]; + strategy: IFeatureStrategy; } const FeatureStrategyExecution = ({ parameters, constraints = [], + strategy, }: IFeatureStrategiesExecutionProps) => { const styles = useStyles(); + const { strategies } = useStrategies(); if (!parameters) return null; + const definition = strategies.find(strategyDefinition => { + return strategyDefinition.name === strategy.name; + }); + const renderConstraints = () => { return constraints.map((constraint, index) => { if (index !== constraints.length - 1) { @@ -42,6 +54,8 @@ const FeatureStrategyExecution = ({ }; const renderParameters = () => { + if (definition?.editable) return null; + return Object.keys(parameters).map((key, index) => { switch (key) { case 'rollout': @@ -67,6 +81,7 @@ const FeatureStrategyExecution = ({ return ( @@ -79,6 +94,7 @@ const FeatureStrategyExecution = ({ return ( @@ -90,29 +106,152 @@ const FeatureStrategyExecution = ({ return ( ); + case 'stickiness': + case 'groupId': + return null; default: return null; } }); }; + const renderCustomStrategy = () => { + if (!definition?.editable) return null; + return definition?.parameters.map((param: any, index: number) => { + const notLastItem = index !== definition?.parameters?.length - 1; + switch (param?.type) { + case 'list': + const values = strategy?.parameters[param.name] + .split(',') + .filter((val: string) => val); + + return ( + + + } + /> + + ); + case 'percentage': + return ( + +

+ {strategy?.parameters[param.name]}% of your user + base{' '} + {constraints?.length > 0 + ? 'who match constraints' + : ''}{' '} + are included. +

+ + + } + /> +
+ ); + case 'boolean': + return ( + +

+ {param.name} must be{' '} + {strategy.parameters[param.name]} +

+ + } + /> + } + /> +
+ ); + case 'string': + const numValue = strategy.parameters[param.name]; + return ( + +

+ {param.name} is set to {numValue} +

+ + } + /> + + } + /> + ); + case 'number': + const value = strategy.parameters[param.name]; + return ( + +

+ {param.name} is set to {value} +

+ + } + /> + + } + /> + ); + case 'default': + return null; + } + return null; + }); + }; + return ( <> 0} show={ -
-

Enabled for match:

- {renderConstraints()} - -
+ <> +
+

Enabled for match:

+ {renderConstraints()} + +
+ } /> + The standard strategy is on for all users.

} + /> {renderParameters()} + {renderCustomStrategy()} ); }; diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyExecution/FeatureStrategyExecutionChips/FeatureStrategyExecutionChips.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyExecution/FeatureStrategyExecutionChips/FeatureStrategyExecutionChips.styles.ts index c96e8cfcd8..e02d725753 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyExecution/FeatureStrategyExecutionChips/FeatureStrategyExecutionChips.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategyExecution/FeatureStrategyExecutionChips/FeatureStrategyExecutionChips.styles.ts @@ -7,5 +7,8 @@ export const useStyles = makeStyles(theme => ({ }, paragraph: { margin: '0.25rem 0', + maxWidth: '95%', + textAlign: 'center', + wordBreak: 'break-word', }, })); diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/DefaultStrategy/DefaultStrategy.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/DefaultStrategy/DefaultStrategy.tsx new file mode 100644 index 0000000000..8352c19c5a --- /dev/null +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/DefaultStrategy/DefaultStrategy.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { IStrategy } from '../../../../../../interfaces/strategy'; + +interface IDefaultStrategyProps { + strategyDefinition: IStrategy; +} + +const DefaultStrategy = ({ strategyDefinition }: IDefaultStrategyProps) => { + return
{strategyDefinition?.description}
; +}; + +export default DefaultStrategy; diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/FlexibleStrategy/FlexibleStrategy.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/FlexibleStrategy/FlexibleStrategy.tsx index 5ad30fadf7..25b4221eda 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/FlexibleStrategy/FlexibleStrategy.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/FlexibleStrategy/FlexibleStrategy.tsx @@ -6,6 +6,10 @@ import RolloutSlider from '../RolloutSlider/RolloutSlider'; import Select from '../../../../../common/select'; import React from 'react'; import Input from '../../../../../common/Input/Input'; +import { + FLEXIBLE_STRATEGY_GROUP_ID, + FLEXIBLE_STRATEGY_STICKINESS_ID, +} from '../../../../../../testIds'; const builtInStickinessOptions = [ { key: 'default', label: 'default' }, @@ -64,6 +68,7 @@ const FlexibleStrategy = ({ value={1 * rollout} onChange={updateRollout} /> +
@@ -91,6 +96,7 @@ const FlexibleStrategy = ({ label="Stickiness" options={stickinessOptions} value={stickiness} + data-test={FLEXIBLE_STRATEGY_STICKINESS_ID} onChange={e => onUpdate('stickiness')(e, e.target.value as number) } @@ -121,6 +127,7 @@ const FlexibleStrategy = ({ label="groupId" value={groupId || ''} onChange={e => onUpdate('groupId')(e, e.target.value)} + data-test={FLEXIBLE_STRATEGY_GROUP_ID} />
diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/GeneralStrategy/GeneralStrategy.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/GeneralStrategy/GeneralStrategy.tsx index abf4f2d0ca..07dbc6fd90 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/GeneralStrategy/GeneralStrategy.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/GeneralStrategy/GeneralStrategy.tsx @@ -100,7 +100,7 @@ const GeneralStrategy = ({ return (
({ slider: { - width: 450, + width: '100%', maxWidth: '100%', }, margin: { @@ -99,6 +100,7 @@ const RolloutSlider = ({ getAriaValueText={valuetext} aria-labelledby="discrete-slider-always" step={1} + data-test={ROLLOUT_SLIDER_ID} marks={marks} onChange={onChange} valueLabelDisplay="on" diff --git a/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/StrategyInputList/StrategyInputList.tsx b/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/StrategyInputList/StrategyInputList.tsx index d86b1738ed..cdd857aa16 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/StrategyInputList/StrategyInputList.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureStrategies/common/StrategyInputList/StrategyInputList.tsx @@ -2,6 +2,10 @@ import React, { ChangeEvent, useState } from 'react'; import { Button, Chip, TextField, Typography } from '@material-ui/core'; import { Add } from '@material-ui/icons'; import ConditionallyRender from '../../../../../common/ConditionallyRender'; +import { + ADD_TO_STRATEGY_INPUT_LIST, + STRATEGY_INPUT_LIST, +} from '../../../../../../testIds'; interface IStrategyInputList { name: string; @@ -93,9 +97,11 @@ const StrategyInputList = ({ onBlur={onBlur} onChange={onChange} onKeyDown={onKeyDown} + data-test={STRATEGY_INPUT_LIST} /> diff --git a/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap b/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap index 035cb9e53b..e5c87bfe99 100644 --- a/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap +++ b/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap @@ -166,6 +166,7 @@ exports[`renders correctly with one strategy without permissions 1`] = ` > diff --git a/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx b/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx index 2055f75f31..d79ef045fc 100644 --- a/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx +++ b/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx @@ -10,6 +10,11 @@ import useQueryParams from '../../../hooks/useQueryParams'; import AuthOptions from '../common/AuthOptions/AuthOptions'; import DividerText from '../../common/DividerText/DividerText'; import { Alert } from '@material-ui/lab'; +import { + LOGIN_BUTTON, + LOGIN_PASSWORD_ID, + LOGIN_EMAIL_ID, +} from '../../../testIds'; const PasswordAuth = ({ authDetails, passwordLogin }) => { const commonStyles = useCommonStyles(); @@ -101,7 +106,7 @@ const PasswordAuth = ({ authDetails, passwordLogin }) => { variant="outlined" autoComplete="true" size="small" - data-test="LI_EMAIL_ID" + data-test={LOGIN_EMAIL_ID} /> { variant="outlined" autoComplete="true" size="small" - data-test="LI_PASSWORD_ID" + data-test={LOGIN_PASSWORD_ID} /> diff --git a/frontend/src/hooks/useTabs.ts b/frontend/src/hooks/useTabs.ts index 4a00da83bb..8cf09fa579 100644 --- a/frontend/src/hooks/useTabs.ts +++ b/frontend/src/hooks/useTabs.ts @@ -1,14 +1,14 @@ import { useState } from 'react'; const useTabs = (startingIndex: number = 0) => { - const [activeTab, setActiveTab] = useState(startingIndex); + const [activeTabIdx, setActiveTab] = useState(startingIndex); const a11yProps = (index: number) => ({ id: `tab-${index}`, 'aria-controls': `tabpanel-${index}`, }); - return { activeTab, setActiveTab, a11yProps }; + return { activeTabIdx, setActiveTab, a11yProps }; }; export default useTabs; diff --git a/frontend/src/interfaces/params.ts b/frontend/src/interfaces/params.ts index 74685fe731..a58af34e11 100644 --- a/frontend/src/interfaces/params.ts +++ b/frontend/src/interfaces/params.ts @@ -1,4 +1,5 @@ export interface IFeatureViewParams { projectId: string; featureId: string; + activeTab: string; } diff --git a/frontend/src/interfaces/strategy.ts b/frontend/src/interfaces/strategy.ts index dc79fc043a..876e5def38 100644 --- a/frontend/src/interfaces/strategy.ts +++ b/frontend/src/interfaces/strategy.ts @@ -11,6 +11,7 @@ export interface IStrategy { editable: boolean; deprecated: boolean; description: string; + parameters: IParameter; } export interface IConstraint { @@ -31,5 +32,3 @@ export interface IStrategyPayload { constraints: IConstraint[]; parameters: IParameter; } - - diff --git a/frontend/src/testIds.js b/frontend/src/testIds.js index 29da42cd63..1df90a2c39 100644 --- a/frontend/src/testIds.js +++ b/frontend/src/testIds.js @@ -1,7 +1,34 @@ export const REPORTING_SELECT_ID = 'REPORTING_SELECT_ID'; +/* NAVIGATION */ +export const NAVIGATE_TO_CREATE_FEATURE = 'NAVIGATE_TO_CREATE_FEATURE'; + /* CREATE FEATURE */ export const CF_NAME_ID = 'CF_NAME_ID'; export const CF_TYPE_ID = 'CF_TYPE_ID'; export const CF_DESC_ID = 'CF_DESC_ID'; export const CF_CREATE_BTN_ID = 'CF_CREATE_BTN_ID'; + +/* LOGIN */ +export const LOGIN_EMAIL_ID = 'LOGIN_EMAIL_ID'; +export const LOGIN_BUTTON = 'LOGIN_BUTTON'; +export const LOGIN_PASSWORD_ID = 'LOGIN_PASSWORD_ID'; + +/* STRATEGY */ +export const ADD_NEW_STRATEGY_ID = 'ADD_NEW_STRATEGY_ID'; +export const ADD_NEW_STRATEGY_CARD_BUTTON_ID = + 'ADD_NEW_STRATEGY_CARD_BUTTON_ID'; +export const ROLLOUT_SLIDER_ID = 'ROLLOUT_SLIDER_ID'; +export const ADD_NEW_STRATEGY_SAVE_ID = 'ADD_NEW_STRATEGY_SAVE_ID'; +export const DIALOGUE_CONFIRM_ID = 'DIALOGUE_CONFIRM_ID'; +export const ADD_CONSTRAINT_ID = 'ADD_CONSTRAINT_ID'; +export const CONSTRAINT_AUTOCOMPLETE_ID = 'CONSTRAINT_AUTOCOMPLETE_ID'; +export const STRATEGY_ACCORDION_ID = 'STRATEGY_ACCORDION_ID'; +export const FLEXIBLE_STRATEGY_STICKINESS_ID = + 'FLEXIBLE_STRATEGY_STICKINESS_ID'; +export const FLEXIBLE_STRATEGY_GROUP_ID = 'FLEXIBLE_STRATEGY_GROUP_ID'; +export const SELECT_ITEM_ID = 'SELECT_ITEM_ID'; +export const UPDATE_STRATEGY_BUTTON_ID = 'UPDATE_STRATEGY_BUTTON_ID'; +export const DELETE_STRATEGY_ID = 'DELETE_STRATEGY_ID'; +export const STRATEGY_INPUT_LIST = 'STRATEGY_INPUT_LIST'; +export const ADD_TO_STRATEGY_INPUT_LIST = 'ADD_TO_STRATEGY_INPUT_LIST'; diff --git a/frontend/src/utils/get-strategy-object.ts b/frontend/src/utils/get-strategy-object.ts new file mode 100644 index 0000000000..ac16a3f939 --- /dev/null +++ b/frontend/src/utils/get-strategy-object.ts @@ -0,0 +1,19 @@ +import { resolveDefaultParamValue } from '../component/feature/strategy/AddStrategy/utils'; +import { IStrategy, IParameter } from '../interfaces/strategy'; + +export const getStrategyObject = ( + selectableStrategies: IStrategy[], + name: string, + featureId: string +) => { + const selectedStrategy = selectableStrategies.find( + strategy => strategy.name === name + ); + const parameters = {} as IParameter; + + selectedStrategy?.parameters.forEach(({ name }: IParameter) => { + parameters[name] = resolveDefaultParamValue(name, featureId); + }); + + return { name, parameters, constraints: [] }; +}; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 8e16c8ef23..1eb8043a97 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1291,6 +1291,39 @@ resolved "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz" integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== +"@cypress/request@^2.88.6": + version "2.88.6" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.6.tgz#a970dd675befc6bdf8a8921576c01f51cc5798e9" + integrity sha512-z0UxBE/+qaESAHY9p9sM2h8Y4XqtsbDCt0/DPOrqA/RZgKi4PkxdpXyK4wCCnSk1xHqWHZZAE+gV6aDAR6+caQ== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^8.3.2" + +"@cypress/xvfb@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" + integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== + dependencies: + debug "^3.1.0" + lodash.once "^4.1.1" + "@emotion/hash@^0.8.0": version "0.8.0" resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz" @@ -2100,6 +2133,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.27.tgz#4141fcad57c332a120591de883e26fe4bb14aaea" integrity sha512-qZdePUDSLAZRXXV234bLBEUM0nAQjoxbcSwp1rqSMUe1rZ47mwU6OjciR/JvF1Oo8mc0ys6GE0ks0HGgqAZoGg== +"@types/node@^14.14.31": + version "14.17.19" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.19.tgz#7341e9ac1b5d748d7a3ddc04336ed536a6f91c31" + integrity sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA== + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz" @@ -2196,6 +2234,16 @@ resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz" integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== +"@types/sinonjs__fake-timers@^6.0.2": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz#0ecc1b9259b76598ef01942f547904ce61a6a77d" + integrity sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A== + +"@types/sizzle@^2.3.2": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" + integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz" @@ -2277,6 +2325,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a" + integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@^4.5.0": version "4.21.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.21.0.tgz" @@ -2673,7 +2728,7 @@ ansi-colors@^4.1.1: resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.1: version "4.3.2" resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -2753,6 +2808,11 @@ aproba@^1.1.1: resolved "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +arch@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" + integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" @@ -2940,6 +3000,11 @@ async@^2.6.2: dependencies: lodash "^4.17.14" +async@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8" + integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" @@ -3243,7 +3308,12 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bluebird@^3.5.5: +blob-util@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" + integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== + +bluebird@^3.5.5, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -3421,6 +3491,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz" @@ -3524,6 +3599,11 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cachedir@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" + integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" @@ -3638,6 +3718,11 @@ char-regex@^1.0.2: resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +check-more-types@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" + integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= + check-types@^11.1.1: version "11.1.2" resolved "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz" @@ -3738,6 +3823,11 @@ ci-info@^2.0.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-info@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" + integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== + cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz" @@ -3778,6 +3868,31 @@ clean-stack@^2.0.0: resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-table3@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" + integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== + dependencies: + object-assign "^4.1.0" + string-width "^4.2.0" + optionalDependencies: + colors "^1.1.2" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz" @@ -3873,6 +3988,16 @@ colorette@^1.2.1, colorette@^1.2.2: resolved "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== +colorette@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" + integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== + +colors@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" @@ -3890,6 +4015,11 @@ commander@^4.1.1: resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + common-tags@^1.8.0: version "1.8.0" resolved "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz" @@ -4447,6 +4577,53 @@ cyclist@^1.0.1: resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= +cypress@^8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.4.1.tgz#8b5898bf49359cadc28f02ba05d51f63b8e3a717" + integrity sha512-itJXq0Vx3sXCUrDyBi2IUrkxVu/gTTp1VhjB5tzGgkeCR8Ae+/T8WV63rsZ7fS8Tpq7LPPXiyoM/sEdOX7cR6A== + dependencies: + "@cypress/request" "^2.88.6" + "@cypress/xvfb" "^1.2.4" + "@types/node" "^14.14.31" + "@types/sinonjs__fake-timers" "^6.0.2" + "@types/sizzle" "^2.3.2" + arch "^2.2.0" + blob-util "^2.0.2" + bluebird "^3.7.2" + cachedir "^2.3.0" + chalk "^4.1.0" + check-more-types "^2.24.0" + cli-cursor "^3.1.0" + cli-table3 "~0.6.0" + commander "^5.1.0" + common-tags "^1.8.0" + dayjs "^1.10.4" + debug "^4.3.2" + enquirer "^2.3.6" + eventemitter2 "^6.4.3" + execa "4.1.0" + executable "^4.1.1" + extract-zip "2.0.1" + figures "^3.2.0" + fs-extra "^9.1.0" + getos "^3.2.1" + is-ci "^3.0.0" + is-installed-globally "~0.4.0" + lazy-ass "^1.6.0" + listr2 "^3.8.3" + lodash "^4.17.21" + log-symbols "^4.0.0" + minimist "^1.2.5" + ospath "^1.2.2" + pretty-bytes "^5.6.0" + ramda "~0.27.1" + request-progress "^3.0.0" + supports-color "^8.1.1" + tmp "~0.2.1" + untildify "^4.0.0" + url "^0.11.0" + yauzl "^2.10.0" + d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz" @@ -4481,6 +4658,11 @@ date-fns@2.19.0: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.19.0.tgz#65193348635a28d5d916c43ec7ce6fbd145059e1" integrity sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg== +dayjs@^1.10.4: + version "1.10.7" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468" + integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig== + debounce@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" @@ -4493,7 +4675,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^3.1.1, debug@^3.2.6: +debug@^3.1.0, debug@^3.1.1, debug@^3.2.6: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -4507,6 +4689,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" +debug@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" @@ -4946,7 +5135,7 @@ enhanced-resolve@^4.3.0, enhanced-resolve@^4.5.0: memory-fs "^0.5.0" tapable "^1.0.0" -enquirer@^2.3.5: +enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -5451,6 +5640,11 @@ etag@~1.8.1: resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +eventemitter2@^6.4.3: + version "6.4.4" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.4.tgz#aa96e8275c4dbeb017a5d0e03780c65612a1202b" + integrity sha512-HLU3NDY6wARrLCEwyGKRBvuWYyvW6mHYv72SJJAH3iJN3a6eVUvkjFkcxah1bcTgGVBBrFdIopBJPhCQFMLyXw== + eventemitter3@^4.0.0: version "4.0.7" resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" @@ -5481,20 +5675,7 @@ exec-sh@^0.3.2: resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz" integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^4.0.0: +execa@4.1.0, execa@^4.0.0: version "4.1.0" resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== @@ -5509,6 +5690,26 @@ execa@^4.0.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +executable@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" + integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== + dependencies: + pify "^2.2.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" @@ -5616,6 +5817,17 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-zip@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" @@ -5674,6 +5886,13 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + fetch-mock@9.11.0: version "9.11.0" resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f" @@ -5695,6 +5914,13 @@ figgy-pudding@^3.5.1: resolved "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== +figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" @@ -5896,7 +6122,7 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.1: +fs-extra@^9.0.1, fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -6002,7 +6228,7 @@ get-stream@^4.0.0: dependencies: pump "^3.0.0" -get-stream@^5.0.0: +get-stream@^5.0.0, get-stream@^5.1.0: version "5.2.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== @@ -6014,6 +6240,13 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +getos@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" + integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== + dependencies: + async "^3.2.0" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz" @@ -6053,6 +6286,13 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +global-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" + integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== + dependencies: + ini "2.0.0" + global-modules@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz" @@ -6642,6 +6882,11 @@ inherits@2.0.3: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + ini@^1.3.5: version "1.3.8" resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" @@ -6763,6 +7008,13 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" +is-ci@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994" + integrity sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ== + dependencies: + ci-info "^3.1.1" + is-color-stop@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz" @@ -6880,6 +7132,14 @@ is-in-browser@^1.0.2, is-in-browser@^1.1.3: resolved "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz" integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= +is-installed-globally@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + is-module@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz" @@ -6936,6 +7196,11 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" @@ -7020,6 +7285,11 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0: resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + is-windows@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" @@ -7823,6 +8093,11 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" +lazy-ass@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" + integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM= + leven@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" @@ -7849,6 +8124,19 @@ lines-and-columns@^1.1.6: resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +listr2@^3.8.3: + version "3.12.2" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.12.2.tgz#2d55cc627111603ad4768a9e87c9c7bb9b49997e" + integrity sha512-64xC2CJ/As/xgVI3wbhlPWVPx0wfTqbUAkpb7bjDi0thSWMqrf07UFhrfsGoo8YSXmF049Rp9C0cjLC8rZxK9A== + dependencies: + cli-truncate "^2.1.0" + colorette "^1.4.0" + log-update "^4.0.0" + p-map "^4.0.0" + rxjs "^6.6.7" + through "^2.3.8" + wrap-ansi "^7.0.0" + load-json-file@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz" @@ -7969,6 +8257,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.once@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz" @@ -8004,6 +8297,24 @@ lodash.uniq@^4.5.0: resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-symbols@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + loglevel@^1.6.8: version "1.7.1" resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz" @@ -8807,6 +9118,11 @@ os-browserify@^0.3.0: resolved "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= +ospath@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" + integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz" @@ -9068,6 +9384,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" @@ -9078,7 +9399,7 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== -pify@^2.0.0: +pify@^2.0.0, pify@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= @@ -9884,7 +10205,7 @@ prepend-http@^1.0.0: resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -pretty-bytes@^5.3.0: +pretty-bytes@^5.3.0, pretty-bytes@^5.6.0: version "5.6.0" resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== @@ -10108,6 +10429,11 @@ railroad-diagrams@^1.0.0: resolved "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz" integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234= +ramda@~0.27.1: + version "0.27.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" + integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== + randexp@0.4.6: version "0.4.6" resolved "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz" @@ -10631,6 +10957,13 @@ repeat-string@^1.6.1: resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +request-progress@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" + integrity sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4= + dependencies: + throttleit "^1.0.0" + request-promise-core@1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz" @@ -10772,6 +11105,14 @@ resolve@^2.0.0-next.3: is-core-module "^2.2.0" path-parse "^1.0.6" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + ret@~0.1.10: version "0.1.15" resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz" @@ -10894,6 +11235,13 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" +rxjs@^6.6.7: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" @@ -11207,6 +11555,15 @@ slash@^3.0.0: resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" @@ -11689,6 +12046,13 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-hyperlinks@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz" @@ -11871,6 +12235,11 @@ throat@^5.0.0: resolved "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +throttleit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" + integrity sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw= + through2@^2.0.0: version "2.0.5" resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" @@ -11879,6 +12248,11 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + thunky@^1.0.2: version "1.1.0" resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz" @@ -11906,6 +12280,13 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== +tmp@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmpl@1.0.x: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -12229,6 +12610,11 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + upath@^1.1.1, upath@^1.1.2, upath@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz" @@ -12328,7 +12714,7 @@ uuid@^3.3.2, uuid@^3.4.0: resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.0: +uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -12873,6 +13259,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" @@ -12984,6 +13379,14 @@ yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"