diff --git a/frontend/.github/workflows/release_to_cdn.yml b/frontend/.github/workflows/release_to_cdn.yml new file mode 100644 index 0000000000..018b0735df --- /dev/null +++ b/frontend/.github/workflows/release_to_cdn.yml @@ -0,0 +1,34 @@ +name: 'Release static assets to CDN' +on: + push: + tags: + - 'v*' + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [14.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + - run: yarn + - uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_DEFAULT_REGION }} + - name: Get the version + id: get_version + run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} + - name: Deploy static assets to S3 + run: | + aws s3 cp build/ s3://getunleash-static/unleash/${{ steps.get_version.outputs.VERSION }} --recursive + diff --git a/frontend/package.json b/frontend/package.json index 1a050f6643..c7ba64a9b5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "unleash-frontend", "description": "unleash your features", - "version": "4.4.0", + "version": "4.4.1", "keywords": [ "unleash", "feature toggle", @@ -45,11 +45,11 @@ "@testing-library/user-event": "13.5.0", "@types/debounce": "1.2.1", "@types/deep-diff": "1.0.1", - "@types/enzyme": "3.10.10", + "@types/enzyme": "3.10.11", "@types/enzyme-adapter-react-16": "1.0.6", - "@types/jest": "27.0.3", - "@types/node": "14.18.2", - "@types/react": "17.0.37", + "@types/jest": "27.4.0", + "@types/node": "14.18.5", + "@types/react": "17.0.38", "@types/react-dom": "17.0.11", "@types/react-router-dom": "5.3.2", "@types/react-timeago": "4.1.3", @@ -60,7 +60,7 @@ "craco": "0.0.3", "css-loader": "6.5.1", "cypress": "8.7.0", - "date-fns": "2.27.0", + "date-fns": "2.28.0", "debounce": "1.2.1", "deep-diff": "1.0.2", "enzyme": "3.11.0", @@ -75,8 +75,8 @@ "node-fetch": "2.6.6", "prettier": "2.5.1", "react": "17.0.2", - "react-dnd": "14.0.4", - "react-dnd-html5-backend": "14.0.2", + "react-dnd": "14.0.5", + "react-dnd-html5-backend": "14.1.0", "react-dom": "17.0.2", "react-outside-click-handler": "1.3.0", "react-redux": "7.2.6", @@ -87,7 +87,7 @@ "redux-devtools-extension": "2.13.9", "redux-mock-store": "1.5.4", "redux-thunk": "2.4.1", - "sass": "1.45.1", + "sass": "1.46.0", "swr": "1.0.1", "typescript": "4.5.4", "web-vitals": "2.1.2" diff --git a/frontend/public/index.html b/frontend/public/index.html index 3006ccce7d..3206f3640c 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -5,6 +5,7 @@ + Unleash - Enterprise ready feature toggles diff --git a/frontend/src/component/admin/users/change-password-component.jsx b/frontend/src/component/admin/users/change-password-component.jsx index cb040e043b..baaf88d426 100644 --- a/frontend/src/component/admin/users/change-password-component.jsx +++ b/frontend/src/component/admin/users/change-password-component.jsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { TextField, Typography, Avatar } from '@material-ui/core'; +import { Typography, Avatar } from '@material-ui/core'; import { trim } from '../../common/util'; import { modalStyles } from './util'; import Dialogue from '../../common/Dialogue/Dialogue'; @@ -10,6 +10,7 @@ import { useCommonStyles } from '../../../common.styles'; import PasswordMatcher from '../../user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher'; import ConditionallyRender from '../../common/ConditionallyRender'; import { Alert } from '@material-ui/lab'; +import PasswordField from '../../common/PasswordField/PasswordField'; function ChangePassword({ showDialog, @@ -109,26 +110,20 @@ function ChangePassword({ />

{error.general}

- - { + const [showPassword, setShowPassword] = useState(false); + + const handleClickShowPassword = () => { + setShowPassword(!showPassword); + }; + + const handleMouseDownPassword = ( + e: React.MouseEvent + ) => { + e.preventDefault(); + }; + + return ( + + + {showPassword ? : } + + + ), + }} + {...rest} + /> + ); +}; + +export default PasswordField; diff --git a/frontend/src/component/common/RolloutIcon/RolloutIcon.styles.ts b/frontend/src/component/common/RolloutIcon/RolloutIcon.styles.ts index 21b4e55997..d6798c6f98 100644 --- a/frontend/src/component/common/RolloutIcon/RolloutIcon.styles.ts +++ b/frontend/src/component/common/RolloutIcon/RolloutIcon.styles.ts @@ -7,6 +7,7 @@ export const useStyles = makeStyles(theme => ({ position: 'relative', width: '50px', height: '100%', + padding: '15px 0px', }, vertical: { borderRadius: '1px', diff --git a/frontend/src/component/context/form-context-component.jsx b/frontend/src/component/context/form-context-component.jsx index e5a1d2df48..3616acf8d6 100644 --- a/frontend/src/component/context/form-context-component.jsx +++ b/frontend/src/component/context/form-context-component.jsx @@ -269,7 +269,7 @@ class AddContextComponent extends Component { specific values of this context field. PS! Not all client SDK's support this feature yet!{' '} diff --git a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts index 835ff44f51..1a3b2e2aa9 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts @@ -19,6 +19,13 @@ export const useStyles = makeStyles(theme => ({ position: 'relative', paddingBottom: '1rem', }, + header: { + display: 'flex', + justifyContent: 'center', + flexDirection: 'column', + + paddingTop: '1.5rem', + }, headerTitle: { display: 'flex', alignItems: 'center', @@ -115,6 +122,29 @@ export const useStyles = makeStyles(theme => ({ fill: theme.palette.grey[400], marginBottom: '1rem', }, + strategiesText: { + fontSize: '14px', + color: theme.palette.grey[700], + }, + stratigiesInfoContainer: { + display: 'flex', + }, + noStratigiesInfoContainer: { + top: '1px', + position: 'relative', + }, + stratigiesIconsContainer: { + display: 'flex', + alignItems: 'center', + transform: 'scale(0.8)', + top: '3px', + left: '-10px', + position: 'relative', + [theme.breakpoints.down(560)]: { + marginLeft: '0px', + top: '5px', + }, + }, [theme.breakpoints.down(750)]: { accordionBodyFooter: { flexDirection: 'column', @@ -125,7 +155,7 @@ export const useStyles = makeStyles(theme => ({ }, [theme.breakpoints.down(560)]: { disabledIndicatorPos: { - top: '-8px', + top: '13px', }, headerTitle: { flexDirection: 'column', @@ -149,6 +179,46 @@ export const useStyles = makeStyles(theme => ({ padding: '0.5rem', }, }, + strategyIconContainer: { + marginRight: '5px', + }, + strategyIcon: { + fill: theme.palette.grey[600], + }, + container: { + display: 'flex', + alignItems: 'center', + marginLeft: '1.3rem', + [theme.breakpoints.down(560)]: { + flexDirection: 'column', + marginLeft: '0', + }, + }, + addStrategyButton: { + background: 'none', + textDecoration: 'none', + boxShadow: 'none', + color: theme.palette.primary.main, + fontWeight: 'normal', + '&:hover': { + background: 'none', + textDecoration: 'none', + boxShadow: 'none', + color: theme.palette.primary.main, + fontWeight: 'normal', + }, + '&:disabled': { + margin: '0px 16px', + height: '35px' + }, + }, + separtor: { + marginLeft: '-10px', + marginRight: '9px', + [theme.breakpoints.down(560)]: { + display: 'none', + }, + }, resultContainer: { display: 'flex', width: '100%', diff --git a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx index 66bd45038e..91b7624ac9 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx @@ -2,24 +2,38 @@ import { Accordion, AccordionDetails, AccordionSummary, + Tooltip, } from '@material-ui/core'; import { ExpandMore } from '@material-ui/icons'; +import React from 'react'; import { useParams } from 'react-router'; +import { useHistory } from 'react-router-dom'; import useFeature from '../../../../../../hooks/api/getters/useFeature/useFeature'; import useFeatureMetrics from '../../../../../../hooks/api/getters/useFeatureMetrics/useFeatureMetrics'; import { IFeatureEnvironment } from '../../../../../../interfaces/featureToggle'; import { IFeatureViewParams } from '../../../../../../interfaces/params'; import { getFeatureMetrics } from '../../../../../../utils/get-feature-metrics'; +import { + getFeatureStrategyIcon, + getHumanReadableStrategyName, +} from '../../../../../../utils/strategy-names'; import ConditionallyRender from '../../../../../common/ConditionallyRender'; import DisabledIndicator from '../../../../../common/DisabledIndicator/DisabledIndicator'; import EnvironmentIcon from '../../../../../common/EnvironmentIcon/EnvironmentIcon'; +import PermissionButton from '../../../../../common/PermissionButton/PermissionButton'; import StringTruncator from '../../../../../common/StringTruncator/StringTruncator'; +import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions'; import { useStyles } from './FeatureOverviewEnvironment.styles'; import FeatureOverviewEnvironmentBody from './FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody'; import FeatureOverviewEnvironmentFooter from './FeatureOverviewEnvironmentFooter/FeatureOverviewEnvironmentFooter'; import FeatureOverviewEnvironmentMetrics from './FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics'; +interface IStrategyIconObject { + count: number; + Icon: React.ReactElement; + name: string; +} interface IFeatureOverviewEnvironmentProps { env: IFeatureEnvironment; } @@ -31,6 +45,7 @@ const FeatureOverviewEnvironment = ({ const { projectId, featureId } = useParams(); const { metrics } = useFeatureMetrics(projectId, featureId); const { feature } = useFeature(projectId, featureId); + const history = useHistory(); const featureMetrics = getFeatureMetrics(feature?.environments, metrics); const environmentMetric = featureMetrics.find( @@ -48,6 +63,29 @@ const FeatureOverviewEnvironment = ({ return `This environment is disabled, which means that none of your strategies are executing`; }; + const strategiesLink = `/projects/${projectId}/features2/${featureId}/strategies?environment=${featureEnvironment?.name}&addStrategy=true`; + + const getStrategyIcons = () => { + const strategyObjects = featureEnvironment?.strategies.reduce( + (acc, current) => { + if (acc[current.name]) { + acc[current.name].count = acc[current.name].count + 1; + } else { + acc[current.name] = { + count: 1, + Icon: getFeatureStrategyIcon(current.name), + }; + } + return acc; + }, + {} as { [key: string]: IStrategyIconObject } + ); + + return Object.keys(strategyObjects).map(strategyName => { + return { ...strategyObjects[strategyName], name: strategyName }; + }); + }; + return (
@@ -55,17 +93,76 @@ const FeatureOverviewEnvironment = ({ className={styles.accordionHeader} expandIcon={} > -
- - Feature toggle execution for  - +
+
+ + Feature toggle execution for  + +
+
+ history.push(strategiesLink)} + className={styles.addStrategyButton} + > + Add strategy + + | + + {getStrategyIcons()?.map( + ({ name, Icon }) => ( + +
+ +
+
+ ) + )} +
+ } + elseShow={ +
+

+ No strategies defined on this toggle +

+
+ } + /> +
+ string; @@ -39,7 +42,14 @@ const FeatureOverviewEnvironmentBody = ({ show={ <>
- Edit strategies + history.push(strategiesLink)} + maxWidth="700px" + permission={UPDATE_FEATURE} + > + Add strategy +
{ const commonStyles = useCommonStyles(); @@ -109,16 +110,13 @@ const HostedAuth = ({ authDetails, passwordLogin }) => { variant="outlined" size="small" /> - setPassword(evt.target.value)} name="password" - type="password" value={password} error={!!passwordError} helperText={passwordError} - variant="outlined" - size="small" />